aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcrobibero <cody@robibe.ro>2020-07-21 08:03:09 -0600
committercrobibero <cody@robibe.ro>2020-07-21 08:03:09 -0600
commitb040d89e5cda3d49ecb45e5441053dc61872f272 (patch)
tree81455a2f97d10c06c20a491a5c46e75fbac1fb47
parent0740ec611211cb121a2ea4f97ab43b92d6411d4d (diff)
parent5b57c81ee14ce585161b9ac331e6e3528826b815 (diff)
Merge remote-tracking branch 'upstream/api-migration' into api-image-service
-rw-r--r--.ci/azure-pipelines-package.yml163
-rw-r--r--.ci/azure-pipelines.yml6
-rw-r--r--.gitignore3
-rw-r--r--.vscode/launch.json12
-rw-r--r--.vscode/tasks.json7
-rw-r--r--DvdLib/Ifo/Cell.cs1
-rw-r--r--DvdLib/Ifo/Chapter.cs2
-rw-r--r--DvdLib/Ifo/Dvd.cs16
-rw-r--r--DvdLib/Ifo/DvdTime.cs10
-rw-r--r--DvdLib/Ifo/ProgramChain.cs15
-rw-r--r--DvdLib/Ifo/Title.cs9
-rw-r--r--Emby.Dlna/ContentDirectory/ControlHandler.cs7
-rw-r--r--Emby.Dlna/Didl/DidlBuilder.cs22
-rw-r--r--Emby.Dlna/Didl/Filter.cs3
-rw-r--r--Emby.Dlna/DlnaManager.cs32
-rw-r--r--Emby.Dlna/Eventing/EventManager.cs28
-rw-r--r--Emby.Dlna/Eventing/EventSubscription.cs3
-rw-r--r--Emby.Dlna/Main/DlnaEntryPoint.cs46
-rw-r--r--Emby.Dlna/PlayTo/Device.cs46
-rw-r--r--Emby.Dlna/PlayTo/PlayToController.cs5
-rw-r--r--Emby.Dlna/PlayTo/PlayToManager.cs15
-rw-r--r--Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs1
-rw-r--r--Emby.Dlna/PlayTo/SsdpHttpClient.cs1
-rw-r--r--Emby.Dlna/PlayTo/uBaseObject.cs2
-rw-r--r--Emby.Dlna/Profiles/DefaultProfile.cs2
-rw-r--r--Emby.Dlna/Profiles/DenonAvrProfile.cs2
-rw-r--r--Emby.Dlna/Profiles/DirectTvProfile.cs2
-rw-r--r--Emby.Dlna/Profiles/Foobar2000Profile.cs2
-rw-r--r--Emby.Dlna/Profiles/MarantzProfile.cs2
-rw-r--r--Emby.Dlna/Profiles/MediaMonkeyProfile.cs3
-rw-r--r--Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs3
-rw-r--r--Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs3
-rw-r--r--Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs3
-rw-r--r--Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs3
-rw-r--r--Emby.Dlna/Server/DescriptionXmlBuilder.cs5
-rw-r--r--Emby.Dlna/Service/BaseControlHandler.cs4
-rw-r--r--Emby.Dlna/Service/ServiceXmlBuilder.cs1
-rw-r--r--Emby.Dlna/Ssdp/DeviceDiscovery.cs2
-rw-r--r--Emby.Naming/AudioBook/AudioBookFilePathParser.cs1
-rw-r--r--Emby.Naming/Common/MediaType.cs6
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs39
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs2
-rw-r--r--Emby.Server.Implementations/Collections/CollectionManager.cs56
-rw-r--r--Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs57
-rw-r--r--Emby.Server.Implementations/ConfigurationOptions.cs1
-rw-r--r--Emby.Server.Implementations/Data/BaseSqliteRepository.cs5
-rw-r--r--Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs1
-rw-r--r--Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs6
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs231
-rw-r--r--Emby.Server.Implementations/Data/SqliteUserDataRepository.cs11
-rw-r--r--Emby.Server.Implementations/Devices/DeviceManager.cs3
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs40
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj14
-rw-r--r--Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs1
-rw-r--r--Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs2
-rw-r--r--Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs14
-rw-r--r--Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs2
-rw-r--r--Emby.Server.Implementations/HttpServer/FileWriter.cs4
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpListenerHost.cs3
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpResultFactory.cs14
-rw-r--r--Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs181
-rw-r--r--Emby.Server.Implementations/HttpServer/ResponseFilter.cs4
-rw-r--r--Emby.Server.Implementations/IO/LibraryMonitor.cs2
-rw-r--r--Emby.Server.Implementations/IO/ManagedFileSystem.cs14
-rw-r--r--Emby.Server.Implementations/IStartupOptions.cs5
-rw-r--r--Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs1
-rw-r--r--Emby.Server.Implementations/Images/DynamicImageProvider.cs1
-rw-r--r--Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs14
-rw-r--r--Emby.Server.Implementations/Library/ExclusiveLiveStream.cs2
-rw-r--r--Emby.Server.Implementations/Library/IgnorePatterns.cs50
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs99
-rw-r--r--Emby.Server.Implementations/Library/LiveStreamHelper.cs7
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs14
-rw-r--r--Emby.Server.Implementations/Library/ResolverHelper.cs2
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs4
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs2
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs4
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs4
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs2
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs5
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs1
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs5
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs2
-rw-r--r--Emby.Server.Implementations/Library/SearchEngine.cs2
-rw-r--r--Emby.Server.Implementations/Library/UserDataManager.cs4
-rw-r--r--Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs1
-rw-r--r--Emby.Server.Implementations/Library/Validators/StudiosValidator.cs1
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs1
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs10
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs1
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs127
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs1
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs32
-rw-r--r--Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs4
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs37
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs14
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs5
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs1
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs3
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs20
-rw-r--r--Emby.Server.Implementations/Localization/Core/af.json10
-rw-r--r--Emby.Server.Implementations/Localization/Core/ar.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/bn.json19
-rw-r--r--Emby.Server.Implementations/Localization/Core/de.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/es-AR.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/es-MX.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/es_419.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/fr-CA.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/hr.json52
-rw-r--r--Emby.Server.Implementations/Localization/Core/mr.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/ms.json64
-rw-r--r--Emby.Server.Implementations/Localization/Core/ne.json86
-rw-r--r--Emby.Server.Implementations/Localization/Core/pt.json16
-rw-r--r--Emby.Server.Implementations/Localization/Core/th.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-HK.json5
-rw-r--r--Emby.Server.Implementations/Net/SocketFactory.cs7
-rw-r--r--Emby.Server.Implementations/Net/UdpSocket.cs10
-rw-r--r--Emby.Server.Implementations/Networking/NetworkManager.cs200
-rw-r--r--Emby.Server.Implementations/Playlists/PlaylistManager.cs15
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs23
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/TaskManager.cs10
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs2
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs8
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs2
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs4
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs6
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs4
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs8
-rw-r--r--Emby.Server.Implementations/Security/AuthenticationRepository.cs8
-rw-r--r--Emby.Server.Implementations/Services/ResponseHelper.cs6
-rw-r--r--Emby.Server.Implementations/Services/ServiceController.cs11
-rw-r--r--Emby.Server.Implementations/Services/ServiceExec.cs9
-rw-r--r--Emby.Server.Implementations/Services/ServiceHandler.cs2
-rw-r--r--Emby.Server.Implementations/Services/ServiceMethod.cs1
-rw-r--r--Emby.Server.Implementations/Services/ServicePath.cs28
-rw-r--r--Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs4
-rw-r--r--Emby.Server.Implementations/Services/SwaggerService.cs34
-rw-r--r--Emby.Server.Implementations/Services/UrlExtensions.cs2
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs7
-rw-r--r--Emby.Server.Implementations/Session/SessionWebSocketListener.cs7
-rw-r--r--Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs2
-rw-r--r--Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs2
-rw-r--r--Emby.Server.Implementations/Sorting/AlbumComparer.cs2
-rw-r--r--Emby.Server.Implementations/Sorting/CriticRatingComparer.cs2
-rw-r--r--Emby.Server.Implementations/Sorting/DateCreatedComparer.cs6
-rw-r--r--Emby.Server.Implementations/Sorting/DatePlayedComparer.cs2
-rw-r--r--Emby.Server.Implementations/Sorting/NameComparer.cs2
-rw-r--r--Emby.Server.Implementations/Sorting/PlayCountComparer.cs2
-rw-r--r--Emby.Server.Implementations/Sorting/PremiereDateComparer.cs3
-rw-r--r--Emby.Server.Implementations/Sorting/ProductionYearComparer.cs2
-rw-r--r--Emby.Server.Implementations/Sorting/RandomComparer.cs2
-rw-r--r--Emby.Server.Implementations/Sorting/RuntimeComparer.cs6
-rw-r--r--Emby.Server.Implementations/Sorting/SortNameComparer.cs6
-rw-r--r--Emby.Server.Implementations/SyncPlay/SyncPlayController.cs56
-rw-r--r--Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs22
-rw-r--r--Emby.Server.Implementations/TV/TVSeriesManager.cs33
-rw-r--r--Emby.Server.Implementations/Udp/UdpServer.cs13
-rw-r--r--Emby.Server.Implementations/Updates/InstallationManager.cs63
-rw-r--r--Jellyfin.Api/Auth/BaseAuthorizationHandler.cs2
-rw-r--r--Jellyfin.Api/Auth/CustomAuthenticationHandler.cs2
-rw-r--r--Jellyfin.Api/Auth/DownloadPolicy/DownloadHandler.cs1
-rw-r--r--Jellyfin.Api/Controllers/ActivityLogController.cs10
-rw-r--r--Jellyfin.Api/Controllers/AlbumsController.cs8
-rw-r--r--Jellyfin.Api/Controllers/ApiKeyController.cs4
-rw-r--r--Jellyfin.Api/Controllers/ArtistsController.cs488
-rw-r--r--Jellyfin.Api/Controllers/ChannelsController.cs256
-rw-r--r--Jellyfin.Api/Controllers/CollectionController.cs14
-rw-r--r--Jellyfin.Api/Controllers/ConfigurationController.cs4
-rw-r--r--Jellyfin.Api/Controllers/DashboardController.cs3
-rw-r--r--Jellyfin.Api/Controllers/DevicesController.cs8
-rw-r--r--Jellyfin.Api/Controllers/DisplayPreferencesController.cs12
-rw-r--r--Jellyfin.Api/Controllers/EnvironmentController.cs191
-rw-r--r--Jellyfin.Api/Controllers/FilterController.cs13
-rw-r--r--Jellyfin.Api/Controllers/GenresController.cs320
-rw-r--r--Jellyfin.Api/Controllers/ImageByNameController.cs12
-rw-r--r--Jellyfin.Api/Controllers/InstantMixController.cs328
-rw-r--r--Jellyfin.Api/Controllers/ItemRefreshController.cs1
-rw-r--r--Jellyfin.Api/Controllers/ItemUpdateController.cs2
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs592
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs71
-rw-r--r--Jellyfin.Api/Controllers/LibraryStructureController.cs43
-rw-r--r--Jellyfin.Api/Controllers/LiveTvController.cs1236
-rw-r--r--Jellyfin.Api/Controllers/MediaInfoController.cs775
-rw-r--r--Jellyfin.Api/Controllers/MoviesController.cs329
-rw-r--r--Jellyfin.Api/Controllers/MusicGenresController.cs313
-rw-r--r--Jellyfin.Api/Controllers/NotificationsController.cs5
-rw-r--r--Jellyfin.Api/Controllers/PackageController.cs46
-rw-r--r--Jellyfin.Api/Controllers/PersonsController.cs282
-rw-r--r--Jellyfin.Api/Controllers/PlaylistsController.cs18
-rw-r--r--Jellyfin.Api/Controllers/PlaystateController.cs372
-rw-r--r--Jellyfin.Api/Controllers/PluginsController.cs5
-rw-r--r--Jellyfin.Api/Controllers/RemoteImageController.cs4
-rw-r--r--Jellyfin.Api/Controllers/ScheduledTasksController.cs161
-rw-r--r--Jellyfin.Api/Controllers/SearchController.cs14
-rw-r--r--Jellyfin.Api/Controllers/SessionController.cs58
-rw-r--r--Jellyfin.Api/Controllers/StartupController.cs7
-rw-r--r--Jellyfin.Api/Controllers/StudiosController.cs277
-rw-r--r--Jellyfin.Api/Controllers/SubtitleController.cs18
-rw-r--r--Jellyfin.Api/Controllers/SuggestionsController.cs6
-rw-r--r--Jellyfin.Api/Controllers/SystemController.cs2
-rw-r--r--Jellyfin.Api/Controllers/TrailersController.cs307
-rw-r--r--Jellyfin.Api/Controllers/TvShowsController.cs35
-rw-r--r--Jellyfin.Api/Controllers/UserController.cs17
-rw-r--r--Jellyfin.Api/Controllers/UserLibraryController.cs391
-rw-r--r--Jellyfin.Api/Controllers/UserViewsController.cs148
-rw-r--r--Jellyfin.Api/Controllers/VideoAttachmentsController.cs2
-rw-r--r--Jellyfin.Api/Controllers/VideosController.cs8
-rw-r--r--Jellyfin.Api/Controllers/YearsController.cs231
-rw-r--r--Jellyfin.Api/Extensions/DtoExtensions.cs4
-rw-r--r--Jellyfin.Api/Helpers/ProgressiveFileCopier.cs84
-rw-r--r--Jellyfin.Api/Helpers/RequestHelpers.cs83
-rw-r--r--Jellyfin.Api/Helpers/SimilarItemsHelper.cs14
-rw-r--r--Jellyfin.Api/Helpers/TranscodingJobHelper.cs353
-rw-r--r--Jellyfin.Api/Jellyfin.Api.csproj4
-rw-r--r--Jellyfin.Api/Models/EnvironmentDtos/DefaultDirectoryBrowserInfoDto.cs13
-rw-r--r--Jellyfin.Api/Models/EnvironmentDtos/ValidatePathDto.cs23
-rw-r--r--Jellyfin.Api/Models/LiveTvDtos/ChannelMappingOptionsDto.cs36
-rw-r--r--Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs166
-rw-r--r--Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs253
-rw-r--r--Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs212
-rw-r--r--Jellyfin.Api/Models/UserViewDtos/SpecialViewOptionDto.cs18
-rw-r--r--Jellyfin.Data/Entities/Artwork.cs43
-rw-r--r--Jellyfin.Data/Entities/Book.cs3
-rw-r--r--Jellyfin.Data/Entities/BookMetadata.cs34
-rw-r--r--Jellyfin.Data/Entities/Chapter.cs52
-rw-r--r--Jellyfin.Data/Entities/Collection.cs17
-rw-r--r--Jellyfin.Data/Entities/CollectionItem.cs33
-rw-r--r--Jellyfin.Data/Entities/Company.cs42
-rw-r--r--Jellyfin.Data/Entities/CompanyMetadata.cs50
-rw-r--r--Jellyfin.Data/Entities/CustomItem.cs3
-rw-r--r--Jellyfin.Data/Entities/CustomItemMetadata.cs30
-rw-r--r--Jellyfin.Data/Entities/Episode.cs14
-rw-r--r--Jellyfin.Data/Entities/EpisodeMetadata.cs45
-rw-r--r--Jellyfin.Data/Entities/Genre.cs30
-rw-r--r--Jellyfin.Data/Entities/Group.cs2
-rw-r--r--Jellyfin.Data/Entities/Library.cs24
-rw-r--r--Jellyfin.Data/Entities/LibraryItem.cs26
-rw-r--r--Jellyfin.Data/Entities/LibraryRoot.cs37
-rw-r--r--Jellyfin.Data/Entities/MediaFile.cs42
-rw-r--r--Jellyfin.Data/Entities/MediaFileStream.cs26
-rw-r--r--Jellyfin.Data/Entities/Metadata.cs71
-rw-r--r--Jellyfin.Data/Entities/MetadataProvider.cs24
-rw-r--r--Jellyfin.Data/Entities/MetadataProviderId.cs50
-rw-r--r--Jellyfin.Data/Entities/Movie.cs3
-rw-r--r--Jellyfin.Data/Entities/MovieMetadata.cs49
-rw-r--r--Jellyfin.Data/Entities/MusicAlbum.cs3
-rw-r--r--Jellyfin.Data/Entities/MusicAlbumMetadata.cs44
-rw-r--r--Jellyfin.Data/Entities/Person.cs49
-rw-r--r--Jellyfin.Data/Entities/PersonRole.cs32
-rw-r--r--Jellyfin.Data/Entities/Photo.cs3
-rw-r--r--Jellyfin.Data/Entities/PhotoMetadata.cs30
-rw-r--r--Jellyfin.Data/Entities/ProviderMapping.cs26
-rw-r--r--Jellyfin.Data/Entities/Rating.cs31
-rw-r--r--Jellyfin.Data/Entities/RatingSource.cs40
-rw-r--r--Jellyfin.Data/Entities/Release.cs59
-rw-r--r--Jellyfin.Data/Entities/Season.cs14
-rw-r--r--Jellyfin.Data/Entities/SeasonMetadata.cs35
-rw-r--r--Jellyfin.Data/Entities/Series.cs20
-rw-r--r--Jellyfin.Data/Entities/SeriesMetadata.cs49
-rw-r--r--Jellyfin.Data/Entities/Track.cs14
-rw-r--r--Jellyfin.Data/Entities/TrackMetadata.cs30
-rw-r--r--Jellyfin.Data/Enums/PreferenceKind.cs2
-rw-r--r--Jellyfin.Data/Jellyfin.Data.csproj5
-rw-r--r--Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj4
-rw-r--r--Jellyfin.Server.Implementations/JellyfinDb.cs41
-rw-r--r--Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs59
-rw-r--r--Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs12
-rw-r--r--Jellyfin.Server.Implementations/Users/UserManager.cs105
-rw-r--r--Jellyfin.Server/CoreAppHost.cs3
-rw-r--r--Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs2
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj12
-rw-r--r--Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs78
-rw-r--r--Jellyfin.Server/Migrations/IMigrationRoutine.cs5
-rw-r--r--Jellyfin.Server/Migrations/MigrationRunner.cs4
-rw-r--r--Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs45
-rw-r--r--Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs3
-rw-r--r--Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs3
-rw-r--r--Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs3
-rw-r--r--Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs3
-rw-r--r--Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs3
-rw-r--r--Jellyfin.Server/Program.cs19
-rw-r--r--Jellyfin.Server/Startup.cs2
-rw-r--r--Jellyfin.Server/StartupOptions.cs14
-rw-r--r--MediaBrowser.Api/ApiEntryPoint.cs4
-rw-r--r--MediaBrowser.Api/BaseApiService.cs5
-rw-r--r--MediaBrowser.Api/ChannelService.cs341
-rw-r--r--MediaBrowser.Api/Devices/DeviceService.cs104
-rw-r--r--MediaBrowser.Api/EnvironmentService.cs285
-rw-r--r--MediaBrowser.Api/IHasDtoOptions.cs1
-rw-r--r--MediaBrowser.Api/IHasItemFields.cs3
-rw-r--r--MediaBrowser.Api/Images/ImageRequest.cs12
-rw-r--r--MediaBrowser.Api/Images/ImageService.cs12
-rw-r--r--MediaBrowser.Api/LiveTv/LiveTvService.cs1279
-rw-r--r--MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs80
-rw-r--r--MediaBrowser.Api/MediaBrowser.Api.csproj4
-rw-r--r--MediaBrowser.Api/Movies/MoviesService.cs418
-rw-r--r--MediaBrowser.Api/Movies/TrailersService.cs89
-rw-r--r--MediaBrowser.Api/Music/InstantMixService.cs197
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs16
-rw-r--r--MediaBrowser.Api/Playback/Hls/BaseHlsService.cs5
-rw-r--r--MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs18
-rw-r--r--MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs6
-rw-r--r--MediaBrowser.Api/Playback/Hls/VideoHlsService.cs2
-rw-r--r--MediaBrowser.Api/Playback/MediaInfoService.cs10
-rw-r--r--MediaBrowser.Api/Playback/Progressive/AudioService.cs4
-rw-r--r--MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs10
-rw-r--r--MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs6
-rw-r--r--MediaBrowser.Api/Playback/Progressive/VideoService.cs5
-rw-r--r--MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs6
-rw-r--r--MediaBrowser.Api/Playback/StreamRequest.cs6
-rw-r--r--MediaBrowser.Api/Playback/UniversalAudioService.cs21
-rw-r--r--MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs234
-rw-r--r--MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs2
-rw-r--r--MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs4
-rw-r--r--MediaBrowser.Api/SimilarItemsHelper.cs223
-rw-r--r--MediaBrowser.Api/SyncPlay/SyncPlayService.cs104
-rw-r--r--MediaBrowser.Api/System/ActivityLogWebSocketListener.cs4
-rw-r--r--MediaBrowser.Api/TranscodingJob.cs8
-rw-r--r--MediaBrowser.Api/UserLibrary/ArtistsService.cs143
-rw-r--r--MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs388
-rw-r--r--MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs475
-rw-r--r--MediaBrowser.Api/UserLibrary/GenresService.cs140
-rw-r--r--MediaBrowser.Api/UserLibrary/ItemsService.cs514
-rw-r--r--MediaBrowser.Api/UserLibrary/MusicGenresService.cs124
-rw-r--r--MediaBrowser.Api/UserLibrary/PersonsService.cs146
-rw-r--r--MediaBrowser.Api/UserLibrary/PlaystateService.cs456
-rw-r--r--MediaBrowser.Api/UserLibrary/StudiosService.cs132
-rw-r--r--MediaBrowser.Api/UserLibrary/UserLibraryService.cs575
-rw-r--r--MediaBrowser.Api/UserLibrary/UserViewsService.cs146
-rw-r--r--MediaBrowser.Api/UserLibrary/YearsService.cs131
-rw-r--r--MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs56
-rw-r--r--MediaBrowser.Common/Json/JsonDefaults.cs1
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj4
-rw-r--r--MediaBrowser.Common/Net/INetworkManager.cs49
-rw-r--r--MediaBrowser.Common/Plugins/BasePlugin.cs10
-rw-r--r--MediaBrowser.Common/Plugins/IPlugin.cs5
-rw-r--r--MediaBrowser.Common/Updates/IInstallationManager.cs9
-rw-r--r--MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs5
-rw-r--r--MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs3
-rw-r--r--MediaBrowser.Controller/Channels/ChannelItemInfo.cs11
-rw-r--r--MediaBrowser.Controller/Channels/ISearchableChannel.cs2
-rw-r--r--MediaBrowser.Controller/Channels/InternalChannelFeatures.cs2
-rw-r--r--MediaBrowser.Controller/Collections/CollectionCreationOptions.cs1
-rw-r--r--MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs4
-rw-r--r--MediaBrowser.Controller/Drawing/IImageProcessor.cs8
-rw-r--r--MediaBrowser.Controller/Drawing/ImageHelper.cs1
-rw-r--r--MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs7
-rw-r--r--MediaBrowser.Controller/Dto/DtoOptions.cs6
-rw-r--r--MediaBrowser.Controller/Dto/IDtoService.cs2
-rw-r--r--MediaBrowser.Controller/Entities/AggregateFolder.cs3
-rw-r--r--MediaBrowser.Controller/Entities/Audio/Audio.cs4
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs3
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicArtist.cs7
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicGenre.cs8
-rw-r--r--MediaBrowser.Controller/Entities/AudioBook.cs2
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs76
-rw-r--r--MediaBrowser.Controller/Entities/BasePluginFolder.cs2
-rw-r--r--MediaBrowser.Controller/Entities/CollectionFolder.cs9
-rw-r--r--MediaBrowser.Controller/Entities/Extensions.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs47
-rw-r--r--MediaBrowser.Controller/Entities/Genre.cs8
-rw-r--r--MediaBrowser.Controller/Entities/ICollectionFolder.cs2
-rw-r--r--MediaBrowser.Controller/Entities/IHasAspectRatio.cs2
-rw-r--r--MediaBrowser.Controller/Entities/IHasDisplayOrder.cs2
-rw-r--r--MediaBrowser.Controller/Entities/IHasMediaSources.cs2
-rw-r--r--MediaBrowser.Controller/Entities/IHasProgramAttributes.cs8
-rw-r--r--MediaBrowser.Controller/Entities/IHasScreenshots.cs2
-rw-r--r--MediaBrowser.Controller/Entities/IHasSeries.cs3
-rw-r--r--MediaBrowser.Controller/Entities/InternalItemsQuery.cs103
-rw-r--r--MediaBrowser.Controller/Entities/LinkedChild.cs5
-rw-r--r--MediaBrowser.Controller/Entities/Movies/BoxSet.cs4
-rw-r--r--MediaBrowser.Controller/Entities/Movies/Movie.cs2
-rw-r--r--MediaBrowser.Controller/Entities/PeopleHelper.cs1
-rw-r--r--MediaBrowser.Controller/Entities/Person.cs6
-rw-r--r--MediaBrowser.Controller/Entities/Photo.cs11
-rw-r--r--MediaBrowser.Controller/Entities/Share.cs1
-rw-r--r--MediaBrowser.Controller/Entities/Studio.cs8
-rw-r--r--MediaBrowser.Controller/Entities/TV/Episode.cs10
-rw-r--r--MediaBrowser.Controller/Entities/TV/Season.cs7
-rw-r--r--MediaBrowser.Controller/Entities/TV/Series.cs6
-rw-r--r--MediaBrowser.Controller/Entities/Trailer.cs2
-rw-r--r--MediaBrowser.Controller/Entities/UserItemData.cs7
-rw-r--r--MediaBrowser.Controller/Entities/UserView.cs2
-rw-r--r--MediaBrowser.Controller/Entities/UserViewBuilder.cs15
-rw-r--r--MediaBrowser.Controller/Entities/Video.cs13
-rw-r--r--MediaBrowser.Controller/Entities/Year.cs7
-rw-r--r--MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs5
-rw-r--r--MediaBrowser.Controller/Extensions/StringExtensions.cs2
-rw-r--r--MediaBrowser.Controller/IO/FileData.cs6
-rw-r--r--MediaBrowser.Controller/IServerApplicationHost.cs2
-rw-r--r--MediaBrowser.Controller/IServerApplicationPaths.cs16
-rw-r--r--MediaBrowser.Controller/Library/DeleteOptions.cs1
-rw-r--r--MediaBrowser.Controller/Library/IIntroProvider.cs2
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs18
-rw-r--r--MediaBrowser.Controller/Library/ILibraryPostScanTask.cs2
-rw-r--r--MediaBrowser.Controller/Library/ILiveStream.cs5
-rw-r--r--MediaBrowser.Controller/Library/IMetadataSaver.cs2
-rw-r--r--MediaBrowser.Controller/Library/ISearchEngine.cs2
-rw-r--r--MediaBrowser.Controller/Library/IUserDataManager.cs6
-rw-r--r--MediaBrowser.Controller/Library/IUserManager.cs6
-rw-r--r--MediaBrowser.Controller/Library/ItemChangeEventArgs.cs2
-rw-r--r--MediaBrowser.Controller/Library/ItemResolveArgs.cs19
-rw-r--r--MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs11
-rw-r--r--MediaBrowser.Controller/Library/Profiler.cs12
-rw-r--r--MediaBrowser.Controller/Library/SearchHintInfo.cs2
-rw-r--r--MediaBrowser.Controller/Library/TVUtils.cs5
-rw-r--r--MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs2
-rw-r--r--MediaBrowser.Controller/LiveTv/ChannelInfo.cs9
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvManager.cs5
-rw-r--r--MediaBrowser.Controller/LiveTv/ITunerHost.cs1
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvConflictException.cs3
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvProgram.cs4
-rw-r--r--MediaBrowser.Controller/LiveTv/ProgramInfo.cs7
-rw-r--r--MediaBrowser.Controller/LiveTv/RecordingInfo.cs4
-rw-r--r--MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs1
-rw-r--r--MediaBrowser.Controller/LiveTv/TimerInfo.cs7
-rw-r--r--MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs3
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj4
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs602
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs26
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs10
-rw-r--r--MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs19
-rw-r--r--MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs2
-rw-r--r--MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs4
-rw-r--r--MediaBrowser.Controller/Net/AuthenticatedAttribute.cs9
-rw-r--r--MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs14
-rw-r--r--MediaBrowser.Controller/Net/IHttpResultFactory.cs2
-rw-r--r--MediaBrowser.Controller/Net/IHttpServer.cs6
-rw-r--r--MediaBrowser.Controller/Net/IWebSocketListener.cs2
-rw-r--r--MediaBrowser.Controller/Net/SecurityException.cs2
-rw-r--r--MediaBrowser.Controller/Net/StaticResultOptions.cs5
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessageInfo.cs2
-rw-r--r--MediaBrowser.Controller/Persistence/IItemRepository.cs8
-rw-r--r--MediaBrowser.Controller/Persistence/IRepository.cs4
-rw-r--r--MediaBrowser.Controller/Persistence/IUserDataRepository.cs7
-rw-r--r--MediaBrowser.Controller/Plugins/IPluginConfigurationPage.cs8
-rw-r--r--MediaBrowser.Controller/Plugins/IServerEntryPoint.cs1
-rw-r--r--MediaBrowser.Controller/Providers/IExternalId.cs32
-rw-r--r--MediaBrowser.Controller/Providers/IMetadataProvider.cs2
-rw-r--r--MediaBrowser.Controller/Providers/ImageRefreshOptions.cs2
-rw-r--r--MediaBrowser.Controller/Providers/MetadataRefreshMode.cs8
-rw-r--r--MediaBrowser.Controller/Providers/MetadataResult.cs3
-rw-r--r--MediaBrowser.Controller/Providers/VideoContentType.cs6
-rw-r--r--MediaBrowser.Controller/Resolvers/BaseItemResolver.cs4
-rw-r--r--MediaBrowser.Controller/Resolvers/IItemResolver.cs3
-rw-r--r--MediaBrowser.Controller/Resolvers/ResolverPriority.cs10
-rw-r--r--MediaBrowser.Controller/Security/AuthenticationInfo.cs1
-rw-r--r--MediaBrowser.Controller/Session/AuthenticationRequest.cs8
-rw-r--r--MediaBrowser.Controller/Session/ISessionManager.cs8
-rw-r--r--MediaBrowser.Controller/Session/SessionInfo.cs20
-rw-r--r--MediaBrowser.Controller/Sorting/IBaseItemComparer.cs2
-rw-r--r--MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs2
-rw-r--r--MediaBrowser.Controller/Subtitles/SubtitleResponse.cs3
-rw-r--r--MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs12
-rw-r--r--MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs2
-rw-r--r--MediaBrowser.Controller/Sync/SyncedFileInfo.cs1
-rw-r--r--MediaBrowser.Controller/SyncPlay/GroupInfo.cs29
-rw-r--r--MediaBrowser.Controller/SyncPlay/GroupMember.cs2
-rw-r--r--MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs2
-rw-r--r--MediaBrowser.LocalMetadata/BaseXmlProvider.cs63
-rw-r--r--MediaBrowser.LocalMetadata/Images/CollectionFolderLocalImageProvider.cs (renamed from MediaBrowser.LocalMetadata/Images/CollectionFolderImageProvider.cs)19
-rw-r--r--MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs29
-rw-r--r--MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs20
-rw-r--r--MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs146
-rw-r--r--MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj22
-rw-r--r--MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs1086
-rw-r--r--MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs55
-rw-r--r--MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs65
-rw-r--r--MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs8
-rw-r--r--MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs13
-rw-r--r--MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs116
-rw-r--r--MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs25
-rw-r--r--MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs34
-rw-r--r--MediaBrowser.LocalMetadata/XmlProviderUtils.cs13
-rw-r--r--MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs1
-rw-r--r--MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs4
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs82
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs36
-rw-r--r--MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs8
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs76
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/AssParser.cs4
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs8
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs85
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs14
-rw-r--r--MediaBrowser.Model/Channels/ChannelFeatures.cs2
-rw-r--r--MediaBrowser.Model/Channels/ChannelQuery.cs4
-rw-r--r--MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs26
-rw-r--r--MediaBrowser.Model/Configuration/EncodingOptions.cs6
-rw-r--r--MediaBrowser.Model/Configuration/LibraryOptions.cs23
-rw-r--r--MediaBrowser.Model/Configuration/MetadataPluginType.cs2
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs31
-rw-r--r--MediaBrowser.Model/Configuration/UserConfiguration.cs6
-rw-r--r--MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs1
-rw-r--r--MediaBrowser.Model/Dlna/AudioOptions.cs3
-rw-r--r--MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs8
-rw-r--r--MediaBrowser.Model/Dlna/DeviceProfile.cs21
-rw-r--r--MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs101
-rw-r--r--MediaBrowser.Model/Dlna/ProfileCondition.cs1
-rw-r--r--MediaBrowser.Model/Dlna/SortCriteria.cs1
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs38
-rw-r--r--MediaBrowser.Model/Dlna/StreamInfo.cs60
-rw-r--r--MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs8
-rw-r--r--MediaBrowser.Model/Dto/BaseItemDto.cs27
-rw-r--r--MediaBrowser.Model/Dto/BaseItemPerson.cs8
-rw-r--r--MediaBrowser.Model/Dto/ImageOptions.cs2
-rw-r--r--MediaBrowser.Model/Dto/MediaSourceInfo.cs23
-rw-r--r--MediaBrowser.Model/Dto/MetadataEditorInfo.cs4
-rw-r--r--MediaBrowser.Model/Dto/NameIdPair.cs1
-rw-r--r--MediaBrowser.Model/Entities/DisplayPreferences.cs2
-rw-r--r--MediaBrowser.Model/Entities/MediaStream.cs23
-rw-r--r--MediaBrowser.Model/Entities/MediaUrl.cs1
-rw-r--r--MediaBrowser.Model/Entities/MetadataProvider.cs12
-rw-r--r--MediaBrowser.Model/Entities/PackageReviewInfo.cs1
-rw-r--r--MediaBrowser.Model/Entities/VirtualFolderInfo.cs3
-rw-r--r--MediaBrowser.Model/IO/IZipClient.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs1
-rw-r--r--MediaBrowser.Model/LiveTv/ChannelType.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs10
-rw-r--r--MediaBrowser.Model/LiveTv/LiveTvOptions.cs34
-rw-r--r--MediaBrowser.Model/LiveTv/RecordingQuery.cs13
-rw-r--r--MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/TimerInfoDto.cs1
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj2
-rw-r--r--MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs11
-rw-r--r--MediaBrowser.Model/MediaInfo/MediaInfo.cs8
-rw-r--r--MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs6
-rw-r--r--MediaBrowser.Model/Net/HttpException.cs1
-rw-r--r--MediaBrowser.Model/Net/NetworkShare.cs10
-rw-r--r--MediaBrowser.Model/Notifications/NotificationOption.cs2
-rw-r--r--MediaBrowser.Model/Plugins/PluginInfo.cs6
-rw-r--r--MediaBrowser.Model/Providers/ExternalIdInfo.cs32
-rw-r--r--MediaBrowser.Model/Providers/ExternalIdMediaType.cs71
-rw-r--r--MediaBrowser.Model/Providers/RemoteSearchResult.cs1
-rw-r--r--MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs10
-rw-r--r--MediaBrowser.Model/Providers/SubtitleOptions.cs6
-rw-r--r--MediaBrowser.Model/Providers/SubtitleProviderInfo.cs1
-rw-r--r--MediaBrowser.Model/Querying/ItemFields.cs74
-rw-r--r--MediaBrowser.Model/Querying/ItemSortBy.cs2
-rw-r--r--MediaBrowser.Model/Querying/NextUpQuery.cs4
-rw-r--r--MediaBrowser.Model/Querying/QueryFilters.cs5
-rw-r--r--MediaBrowser.Model/Querying/QueryResult.cs2
-rw-r--r--MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs7
-rw-r--r--MediaBrowser.Model/Search/SearchHint.cs1
-rw-r--r--MediaBrowser.Model/Search/SearchQuery.cs11
-rw-r--r--MediaBrowser.Model/Services/ApiMemberAttribute.cs2
-rw-r--r--MediaBrowser.Model/Services/IRequest.cs12
-rw-r--r--MediaBrowser.Model/Services/IRequiresRequestStream.cs2
-rw-r--r--MediaBrowser.Model/Services/IService.cs2
-rw-r--r--MediaBrowser.Model/Services/QueryParamCollection.cs4
-rw-r--r--MediaBrowser.Model/Services/RouteAttribute.cs18
-rw-r--r--MediaBrowser.Model/Session/ClientCapabilities.cs4
-rw-r--r--MediaBrowser.Model/Session/PlayRequest.cs7
-rw-r--r--MediaBrowser.Model/Session/PlaybackProgressInfo.cs2
-rw-r--r--MediaBrowser.Model/Session/PlaybackStopInfo.cs1
-rw-r--r--MediaBrowser.Model/Session/TranscodingInfo.cs8
-rw-r--r--MediaBrowser.Model/Sync/SyncCategory.cs6
-rw-r--r--MediaBrowser.Model/Sync/SyncJob.cs2
-rw-r--r--MediaBrowser.Model/SyncPlay/GroupUpdateType.cs10
-rw-r--r--MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs6
-rw-r--r--MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs13
-rw-r--r--MediaBrowser.Model/SyncPlay/SendCommandType.cs2
-rw-r--r--MediaBrowser.Model/System/SystemInfo.cs2
-rw-r--r--MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs2
-rw-r--r--MediaBrowser.Model/Tasks/ITaskManager.cs2
-rw-r--r--MediaBrowser.Model/Updates/RepositoryInfo.cs20
-rw-r--r--MediaBrowser.Model/Users/UserAction.cs6
-rw-r--r--MediaBrowser.Model/Users/UserPolicy.cs20
-rw-r--r--MediaBrowser.Providers/Books/AudioBookMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Books/BookMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Channels/ChannelMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Folders/FolderMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Folders/UserViewMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Genres/GenreMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Manager/ImageSaver.cs13
-rw-r--r--MediaBrowser.Providers/Manager/ItemImageProvider.cs25
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs25
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs273
-rw-r--r--MediaBrowser.Providers/Manager/ProviderUtils.cs4
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj7
-rw-r--r--MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs6
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs11
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs2
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs12
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs2
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs7
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs2
-rw-r--r--MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs4
-rw-r--r--MediaBrowser.Providers/Movies/MovieExternalIds.cs13
-rw-r--r--MediaBrowser.Providers/Movies/MovieMetadataService.cs4
-rw-r--r--MediaBrowser.Providers/Movies/TrailerMetadataService.cs4
-rw-r--r--MediaBrowser.Providers/Music/AlbumMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Music/ArtistMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Music/AudioMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Music/Extensions.cs2
-rw-r--r--MediaBrowser.Providers/Music/MusicExternalIds.cs8
-rw-r--r--MediaBrowser.Providers/Music/MusicVideoMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/People/PersonMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Photos/PhotoMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs8
-rw-r--r--MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs39
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs44
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs23
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs14
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs8
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs33
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/Configuration/PluginConfiguration.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs24
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs6
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/Plugin.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/Configuration/PluginConfiguration.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs42
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs6
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs7
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs18
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs5
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs9
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs7
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionImages.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionResult.cs8
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/Part.cs6
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/General/Backdrop.cs8
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/General/Crew.cs7
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/General/ExternalIds.cs6
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/General/Genre.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/General/Images.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keyword.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keywords.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/General/Poster.cs8
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/General/Profile.cs6
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/General/Still.cs9
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/General/StillImages.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/General/Video.cs9
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/General/Videos.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/BelongsToCollection.cs5
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Cast.cs7
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Casts.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Country.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/MovieResult.cs31
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCompany.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCountry.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Releases.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/SpokenLanguage.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Trailers.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Youtube.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonImages.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonResult.cs15
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/Search/ExternalIdLookupResult.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/Search/MovieResult.cs15
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/Search/PersonSearchResult.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TmdbSearchResult.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TvResult.cs10
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Cast.cs7
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRating.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRatings.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/TV/CreatedBy.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Credits.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Episode.cs9
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeCredits.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeResult.cs15
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/TV/GuestStar.cs7
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Network.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Season.cs6
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonImages.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonResult.cs12
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeriesResult.cs31
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/GenericTmdbMovieInfo.cs17
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs5
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs9
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs28
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSearch.cs9
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSettings.cs5
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Music/TmdbMusicVideoProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs9
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs5
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs6
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs8
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs14
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs9
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs2
-rw-r--r--MediaBrowser.Providers/Studios/StudioMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Studios/StudiosImageProvider.cs3
-rw-r--r--MediaBrowser.Providers/Subtitles/SubtitleManager.cs6
-rw-r--r--MediaBrowser.Providers/TV/DummySeasonProvider.cs9
-rw-r--r--MediaBrowser.Providers/TV/EpisodeMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/TV/MissingEpisodeProvider.cs6
-rw-r--r--MediaBrowser.Providers/TV/SeasonMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/TV/SeriesMetadataService.cs4
-rw-r--r--MediaBrowser.Providers/TV/TvExternalIds.cs24
-rw-r--r--MediaBrowser.Providers/Videos/VideoMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Years/YearMetadataService.cs2
-rw-r--r--README.md4
-rw-r--r--RSSDP/DeviceAvailableEventArgs.cs19
-rw-r--r--RSSDP/DeviceEventArgs.cs19
-rw-r--r--RSSDP/DeviceUnavailableEventArgs.cs19
-rw-r--r--RSSDP/DiscoveredSsdpDevice.cs20
-rw-r--r--RSSDP/DisposableManagedObjectBase.cs18
-rw-r--r--RSSDP/HttpParserBase.cs80
-rw-r--r--RSSDP/HttpRequestParser.cs42
-rw-r--r--RSSDP/HttpResponseParser.cs39
-rw-r--r--RSSDP/IEnumerableExtensions.cs11
-rw-r--r--RSSDP/ISsdpCommunicationsServer.cs14
-rw-r--r--RSSDP/ISsdpDeviceLocator.cs18
-rw-r--r--RSSDP/RequestReceivedEventArgs.cs13
-rw-r--r--RSSDP/ResponseReceivedEventArgs.cs14
-rw-r--r--RSSDP/SsdpCommunicationsServer.cs77
-rw-r--r--RSSDP/SsdpConstants.cs1
-rw-r--r--RSSDP/SsdpDevice.cs66
-rw-r--r--RSSDP/SsdpDeviceLocator.cs162
-rw-r--r--RSSDP/SsdpDevicePublisher.cs160
-rw-r--r--RSSDP/SsdpEmbeddedDevice.cs13
-rw-r--r--RSSDP/SsdpRootDevice.cs12
-rw-r--r--debian/changelog6
-rw-r--r--debian/control5
-rw-r--r--deployment/Dockerfile.docker.amd6415
-rw-r--r--deployment/Dockerfile.docker.arm6415
-rw-r--r--deployment/Dockerfile.docker.armhf15
-rwxr-xr-xdeployment/build.centos.amd6416
-rwxr-xr-xdeployment/build.debian.amd6415
-rwxr-xr-xdeployment/build.debian.arm6415
-rwxr-xr-xdeployment/build.debian.armhf15
-rwxr-xr-xdeployment/build.fedora.amd6416
-rwxr-xr-xdeployment/build.linux.amd646
-rwxr-xr-xdeployment/build.macos6
-rwxr-xr-xdeployment/build.portable6
-rwxr-xr-xdeployment/build.ubuntu.amd6415
-rwxr-xr-xdeployment/build.ubuntu.arm6415
-rwxr-xr-xdeployment/build.ubuntu.armhf15
-rwxr-xr-xdeployment/build.windows.amd6416
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj12
-rw-r--r--tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj2
-rw-r--r--tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj2
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs2
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs12
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj2
-rw-r--r--tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj2
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj8
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs7
-rw-r--r--tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj4
-rw-r--r--windows/build-jellyfin.ps12
764 files changed, 16346 insertions, 10571 deletions
diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml
new file mode 100644
index 000000000..272111299
--- /dev/null
+++ b/.ci/azure-pipelines-package.yml
@@ -0,0 +1,163 @@
+jobs:
+- job: BuildPackage
+ displayName: 'Build Packages'
+
+ strategy:
+ matrix:
+ CentOS.amd64:
+ BuildConfiguration: centos.amd64
+ Fedora.amd64:
+ BuildConfiguration: fedora.amd64
+ Debian.amd64:
+ BuildConfiguration: debian.amd64
+ Debian.arm64:
+ BuildConfiguration: debian.arm64
+ Debian.armhf:
+ BuildConfiguration: debian.armhf
+ Ubuntu.amd64:
+ BuildConfiguration: ubuntu.amd64
+ Ubuntu.arm64:
+ BuildConfiguration: ubuntu.arm64
+ Ubuntu.armhf:
+ BuildConfiguration: ubuntu.armhf
+ Linux.amd64:
+ BuildConfiguration: linux.amd64
+ Windows.amd64:
+ BuildConfiguration: windows.amd64
+ MacOS:
+ BuildConfiguration: macos
+ Portable:
+ BuildConfiguration: portable
+
+ pool:
+ vmImage: 'ubuntu-latest'
+
+ steps:
+ - script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) deployment'
+ displayName: 'Build Dockerfile'
+
+ - script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="yes" -e BUILD_ID=$(Build.BuildNumber) jellyfin-server-$(BuildConfiguration)'
+ displayName: 'Run Dockerfile (unstable)'
+ condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
+
+ - script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="no" -e BUILD_ID=$(Build.BuildNumber) jellyfin-server-$(BuildConfiguration)'
+ displayName: 'Run Dockerfile (stable)'
+ condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
+
+ - task: PublishPipelineArtifact@1
+ displayName: 'Publish Release'
+ inputs:
+ targetPath: '$(Build.SourcesDirectory)/deployment/dist'
+ artifactName: 'jellyfin-server-$(BuildConfiguration)'
+
+ - task: SSH@0
+ displayName: 'Create target directory on repository server'
+ inputs:
+ sshEndpoint: repository
+ runOptions: 'inline'
+ inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
+
+ - task: CopyFilesOverSSH@0
+ displayName: 'Upload artifacts to repository server'
+ inputs:
+ sshEndpoint: repository
+ sourceFolder: '$(Build.SourcesDirectory)/deployment/dist'
+ contents: '**'
+ targetFolder: '/srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
+
+- job: BuildDocker
+ displayName: 'Build Docker'
+
+ strategy:
+ matrix:
+ amd64:
+ BuildConfiguration: amd64
+ arm64:
+ BuildConfiguration: arm64
+ armhf:
+ BuildConfiguration: armhf
+
+ pool:
+ vmImage: 'ubuntu-latest'
+
+ steps:
+ - task: Docker@2
+ displayName: 'Push Unstable Image'
+ condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
+ inputs:
+ repository: 'jellyfin/jellyfin-server'
+ command: buildAndPush
+ buildContext: '.'
+ Dockerfile: 'deployment/Dockerfile.docker.$(BuildConfiguration)'
+ containerRegistry: Docker Hub
+ tags: |
+ unstable-$(Build.BuildNumber)-$(BuildConfiguration)
+ unstable-$(BuildConfiguration)
+
+ - task: Docker@2
+ displayName: 'Push Stable Image'
+ condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
+ inputs:
+ repository: 'jellyfin/jellyfin-server'
+ command: buildAndPush
+ buildContext: '.'
+ Dockerfile: 'deployment/Dockerfile.docker.$(BuildConfiguration)'
+ containerRegistry: Docker Hub
+ tags: |
+ stable-$(Build.BuildNumber)-$(BuildConfiguration)
+ stable-$(BuildConfiguration)
+
+- job: CollectArtifacts
+ displayName: 'Collect Artifacts'
+ dependsOn:
+ - BuildPackage
+ - BuildDocker
+ condition: and(succeeded('BuildPackage'), succeeded('BuildDocker'))
+
+ pool:
+ vmImage: 'ubuntu-latest'
+
+ steps:
+ - task: SSH@0
+ displayName: 'Update Unstable Repository'
+ condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
+ inputs:
+ sshEndpoint: repository
+ runOptions: 'inline'
+ inline: |
+ sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable
+ rm $0
+ exit
+
+ - task: SSH@0
+ displayName: 'Update Stable Repository'
+ condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
+ inputs:
+ sshEndpoint: repository
+ runOptions: 'inline'
+ inline: |
+ sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber)
+ rm $0
+ exit
+
+- job: PublishNuget
+ displayName: 'Publish NuGet packages'
+ dependsOn:
+ - BuildPackage
+ condition: and(succeeded('BuildPackage'), startsWith(variables['Build.SourceBranch'], 'refs/tags'))
+
+ pool:
+ vmImage: 'ubuntu-latest'
+
+ steps:
+ - task: NuGetCommand@2
+ inputs:
+ command: 'pack'
+ packagesToPack: Jellyfin.Data/Jellyfin.Data.csproj;MediaBrowser.Common/MediaBrowser.Common.csproj;MediaBrowser.Controller/MediaBrowser.Controller.csproj;MediaBrowser.Model/MediaBrowser.Model.csproj;Emby.Naming/Emby.Naming.csproj
+ packDestination: '$(Build.ArtifactStagingDirectory)'
+
+ - task: NuGetCommand@2
+ inputs:
+ command: 'push'
+ packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg'
+ includeNugetOrg: 'true'
diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml
index 3283121e2..0c86c0171 100644
--- a/.ci/azure-pipelines.yml
+++ b/.ci/azure-pipelines.yml
@@ -15,11 +15,13 @@ trigger:
batch: true
jobs:
+- ${{ if not(or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}:
- template: azure-pipelines-main.yml
parameters:
LinuxImage: 'ubuntu-latest'
RestoreBuildProjects: $(RestoreBuildProjects)
+- ${{ if not(or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}:
- template: azure-pipelines-test.yml
parameters:
ImageNames:
@@ -27,6 +29,7 @@ jobs:
Windows: 'windows-latest'
macOS: 'macos-latest'
+- ${{ if not(or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}:
- template: azure-pipelines-abi.yml
parameters:
Packages:
@@ -43,3 +46,6 @@ jobs:
NugetPackageName: Jellyfin.Common
AssemblyFileName: MediaBrowser.Common.dll
LinuxImage: 'ubuntu-latest'
+
+- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
+ - template: azure-pipelines-package.yml
diff --git a/.gitignore b/.gitignore
index 46f036ad9..0df7606ce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,7 +39,6 @@ ProgramData*/
CorePlugins*/
ProgramData-Server*/
ProgramData-UI*/
-MediaBrowser.WebDashboard/jellyfin-web/**
#################
## Visual Studio
@@ -276,4 +275,4 @@ BenchmarkDotNet.Artifacts
# Ignore web artifacts from native builds
web/
web-src.*
-MediaBrowser.WebDashboard/jellyfin-web/
+MediaBrowser.WebDashboard/jellyfin-web
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 73f347c9f..0f698bfa4 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,9 +1,6 @@
{
- // Use IntelliSense to find out which attributes exist for C# debugging
- // Use hover for the description of the existing attributes
- // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
- "version": "0.2.0",
- "configurations": [
+ "version": "0.2.0",
+ "configurations": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
@@ -24,5 +21,8 @@
"request": "attach",
"processId": "${command:pickProcess}"
}
- ,]
+ ],
+ "env": {
+ "DOTNET_CLI_TELEMETRY_OPTOUT": "1"
+ }
}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 2289fd991..7ddc49d5c 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -21,5 +21,10 @@
],
"problemMatcher": "$msCompile"
}
- ]
+ ],
+ "options": {
+ "env": {
+ "DOTNET_CLI_TELEMETRY_OPTOUT": "1"
+ }
+ }
}
diff --git a/DvdLib/Ifo/Cell.cs b/DvdLib/Ifo/Cell.cs
index 2eab400f7..ea0b50e43 100644
--- a/DvdLib/Ifo/Cell.cs
+++ b/DvdLib/Ifo/Cell.cs
@@ -7,6 +7,7 @@ namespace DvdLib.Ifo
public class Cell
{
public CellPlaybackInfo PlaybackInfo { get; private set; }
+
public CellPositionInfo PositionInfo { get; private set; }
internal void ParsePlayback(BinaryReader br)
diff --git a/DvdLib/Ifo/Chapter.cs b/DvdLib/Ifo/Chapter.cs
index 1e69429f8..e786cb553 100644
--- a/DvdLib/Ifo/Chapter.cs
+++ b/DvdLib/Ifo/Chapter.cs
@@ -5,7 +5,9 @@ namespace DvdLib.Ifo
public class Chapter
{
public ushort ProgramChainNumber { get; private set; }
+
public ushort ProgramNumber { get; private set; }
+
public uint ChapterNumber { get; private set; }
public Chapter(ushort pgcNum, ushort programNum, uint chapterNum)
diff --git a/DvdLib/Ifo/Dvd.cs b/DvdLib/Ifo/Dvd.cs
index ca20baa73..361319625 100644
--- a/DvdLib/Ifo/Dvd.cs
+++ b/DvdLib/Ifo/Dvd.cs
@@ -117,12 +117,19 @@ namespace DvdLib.Ifo
uint chapNum = 1;
vtsFs.Seek(baseAddr + offsets[titleNum], SeekOrigin.Begin);
var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum + 1));
- if (t == null) continue;
+ if (t == null)
+ {
+ continue;
+ }
do
{
t.Chapters.Add(new Chapter(vtsRead.ReadUInt16(), vtsRead.ReadUInt16(), chapNum));
- if (titleNum + 1 < numTitles && vtsFs.Position == (baseAddr + offsets[titleNum + 1])) break;
+ if (titleNum + 1 < numTitles && vtsFs.Position == (baseAddr + offsets[titleNum + 1]))
+ {
+ break;
+ }
+
chapNum++;
}
while (vtsFs.Position < (baseAddr + endaddr));
@@ -147,7 +154,10 @@ namespace DvdLib.Ifo
uint vtsPgcOffset = vtsRead.ReadUInt32();
var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum));
- if (t != null) t.AddPgc(vtsRead, startByte + vtsPgcOffset, entryPgc, pgcNum);
+ if (t != null)
+ {
+ t.AddPgc(vtsRead, startByte + vtsPgcOffset, entryPgc, pgcNum);
+ }
}
}
}
diff --git a/DvdLib/Ifo/DvdTime.cs b/DvdLib/Ifo/DvdTime.cs
index 978af90c2..d23140610 100644
--- a/DvdLib/Ifo/DvdTime.cs
+++ b/DvdLib/Ifo/DvdTime.cs
@@ -15,8 +15,14 @@ namespace DvdLib.Ifo
Second = GetBCDValue(data[2]);
Frames = GetBCDValue((byte)(data[3] & 0x3F));
- if ((data[3] & 0x80) != 0) FrameRate = 30;
- else if ((data[3] & 0x40) != 0) FrameRate = 25;
+ if ((data[3] & 0x80) != 0)
+ {
+ FrameRate = 30;
+ }
+ else if ((data[3] & 0x40) != 0)
+ {
+ FrameRate = 25;
+ }
}
private static byte GetBCDValue(byte data)
diff --git a/DvdLib/Ifo/ProgramChain.cs b/DvdLib/Ifo/ProgramChain.cs
index 4860360af..83c0051b9 100644
--- a/DvdLib/Ifo/ProgramChain.cs
+++ b/DvdLib/Ifo/ProgramChain.cs
@@ -22,7 +22,9 @@ namespace DvdLib.Ifo
public readonly List<Cell> Cells;
public DvdTime PlaybackTime { get; private set; }
+
public UserOperation ProhibitedUserOperations { get; private set; }
+
public byte[] AudioStreamControl { get; private set; } // 8*2 entries
public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries
@@ -33,9 +35,11 @@ namespace DvdLib.Ifo
private ushort _goupProgramNumber;
public ProgramPlaybackMode PlaybackMode { get; private set; }
+
public uint ProgramCount { get; private set; }
public byte StillTime { get; private set; }
+
public byte[] Palette { get; private set; } // 16*4 entries
private ushort _commandTableOffset;
@@ -71,8 +75,15 @@ namespace DvdLib.Ifo
StillTime = br.ReadByte();
byte pbMode = br.ReadByte();
- if (pbMode == 0) PlaybackMode = ProgramPlaybackMode.Sequential;
- else PlaybackMode = ((pbMode & 0x80) == 0) ? ProgramPlaybackMode.Random : ProgramPlaybackMode.Shuffle;
+ if (pbMode == 0)
+ {
+ PlaybackMode = ProgramPlaybackMode.Sequential;
+ }
+ else
+ {
+ PlaybackMode = ((pbMode & 0x80) == 0) ? ProgramPlaybackMode.Random : ProgramPlaybackMode.Shuffle;
+ }
+
ProgramCount = (uint)(pbMode & 0x7F);
Palette = br.ReadBytes(64);
diff --git a/DvdLib/Ifo/Title.cs b/DvdLib/Ifo/Title.cs
index abf806d2c..29a0b95c7 100644
--- a/DvdLib/Ifo/Title.cs
+++ b/DvdLib/Ifo/Title.cs
@@ -8,8 +8,11 @@ namespace DvdLib.Ifo
public class Title
{
public uint TitleNumber { get; private set; }
+
public uint AngleCount { get; private set; }
+
public ushort ChapterCount { get; private set; }
+
public byte VideoTitleSetNumber { get; private set; }
private ushort _parentalManagementMask;
@@ -17,6 +20,7 @@ namespace DvdLib.Ifo
private uint _vtsStartSector; // relative to start of entire disk
public ProgramChain EntryProgramChain { get; private set; }
+
public readonly List<ProgramChain> ProgramChains;
public readonly List<Chapter> Chapters;
@@ -55,7 +59,10 @@ namespace DvdLib.Ifo
var pgc = new ProgramChain(pgcNum);
pgc.ParseHeader(br);
ProgramChains.Add(pgc);
- if (entryPgc) EntryProgramChain = pgc;
+ if (entryPgc)
+ {
+ EntryProgramChain = pgc;
+ }
br.BaseStream.Seek(curPos, SeekOrigin.Begin);
}
diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs
index 27585eafa..291de5245 100644
--- a/Emby.Dlna/ContentDirectory/ControlHandler.cs
+++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs
@@ -466,12 +466,12 @@ namespace Emby.Dlna.ContentDirectory
}
else if (search.SearchType == SearchType.Playlist)
{
- //items = items.OfType<Playlist>();
+ // items = items.OfType<Playlist>();
isFolder = true;
}
else if (search.SearchType == SearchType.MusicAlbum)
{
- //items = items.OfType<MusicAlbum>();
+ // items = items.OfType<MusicAlbum>();
isFolder = true;
}
@@ -926,7 +926,7 @@ namespace Emby.Dlna.ContentDirectory
private QueryResult<ServerItem> GetMovieCollections(User user, InternalItemsQuery query)
{
query.Recursive = true;
- //query.Parent = parent;
+ // query.Parent = parent;
query.SetUser(user);
query.IncludeItemTypes = new[] { typeof(BoxSet).Name };
@@ -1357,6 +1357,7 @@ namespace Emby.Dlna.ContentDirectory
internal class ServerItem
{
public BaseItem Item { get; set; }
+
public StubType? StubType { get; set; }
public ServerItem(BaseItem item)
diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs
index 6cedb3ef0..66baa9512 100644
--- a/Emby.Dlna/Didl/DidlBuilder.cs
+++ b/Emby.Dlna/Didl/DidlBuilder.cs
@@ -98,21 +98,21 @@ namespace Emby.Dlna.Didl
{
using (var writer = XmlWriter.Create(builder, settings))
{
- //writer.WriteStartDocument();
+ // writer.WriteStartDocument();
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
- //didl.SetAttribute("xmlns:sec", NS_SEC);
+ // didl.SetAttribute("xmlns:sec", NS_SEC);
WriteXmlRootAttributes(_profile, writer);
WriteItemElement(writer, item, user, context, null, deviceId, filter, streamInfo);
writer.WriteFullEndElement();
- //writer.WriteEndDocument();
+ // writer.WriteEndDocument();
}
return builder.ToString();
@@ -705,13 +705,13 @@ namespace Emby.Dlna.Didl
}
/// <summary>
- /// Adds fields used by both items and folders
+ /// Adds fields used by both items and folders.
/// </summary>
private void AddCommonFields(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter)
{
// Don't filter on dc:title because not all devices will include it in the filter
// MediaMonkey for example won't display content without a title
- //if (filter.Contains("dc:title"))
+ // if (filter.Contains("dc:title"))
{
AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NS_DC);
}
@@ -750,7 +750,7 @@ namespace Emby.Dlna.Didl
AddValue(writer, "dc", "description", desc, NS_DC);
}
}
- //if (filter.Contains("upnp:longDescription"))
+ // if (filter.Contains("upnp:longDescription"))
//{
// if (!string.IsNullOrWhiteSpace(item.Overview))
// {
@@ -765,6 +765,7 @@ namespace Emby.Dlna.Didl
{
AddValue(writer, "dc", "rating", item.OfficialRating, NS_DC);
}
+
if (filter.Contains("upnp:rating"))
{
AddValue(writer, "upnp", "rating", item.OfficialRating, NS_UPNP);
@@ -1052,10 +1053,12 @@ namespace Emby.Dlna.Didl
{
return GetImageInfo(item, ImageType.Primary);
}
+
if (item.HasImage(ImageType.Thumb))
{
return GetImageInfo(item, ImageType.Thumb);
}
+
if (item.HasImage(ImageType.Backdrop))
{
if (item is Channel)
@@ -1135,25 +1138,24 @@ namespace Emby.Dlna.Didl
if (width == 0 || height == 0)
{
- //_imageProcessor.GetImageSize(item, imageInfo);
+ // _imageProcessor.GetImageSize(item, imageInfo);
width = null;
height = null;
}
-
else if (width == -1 || height == -1)
{
width = null;
height = null;
}
- //try
+ // try
//{
// var size = _imageProcessor.GetImageSize(imageInfo);
// width = size.Width;
// height = size.Height;
//}
- //catch
+ // catch
//{
//}
diff --git a/Emby.Dlna/Didl/Filter.cs b/Emby.Dlna/Didl/Filter.cs
index 412259e90..b730d9db2 100644
--- a/Emby.Dlna/Didl/Filter.cs
+++ b/Emby.Dlna/Didl/Filter.cs
@@ -12,7 +12,6 @@ namespace Emby.Dlna.Didl
public Filter()
: this("*")
{
-
}
public Filter(string filter)
@@ -26,7 +25,7 @@ namespace Emby.Dlna.Didl
{
// Don't bother with this. Some clients (media monkey) use the filter and then don't display very well when very little data comes back.
return true;
- //return _all || ListHelper.ContainsIgnoreCase(_fields, field);
+ // return _all || ListHelper.ContainsIgnoreCase(_fields, field);
}
}
}
diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs
index a85e5c35e..5911a73ef 100644
--- a/Emby.Dlna/DlnaManager.cs
+++ b/Emby.Dlna/DlnaManager.cs
@@ -88,7 +88,6 @@ namespace Emby.Dlna
.Select(i => i.Item2)
.ToList();
}
-
}
public DeviceProfile GetDefaultProfile()
@@ -141,55 +140,73 @@ namespace Emby.Dlna
if (!string.IsNullOrEmpty(profileInfo.DeviceDescription))
{
if (deviceInfo.DeviceDescription == null || !IsRegexMatch(deviceInfo.DeviceDescription, profileInfo.DeviceDescription))
+ {
return false;
+ }
}
if (!string.IsNullOrEmpty(profileInfo.FriendlyName))
{
if (deviceInfo.FriendlyName == null || !IsRegexMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName))
+ {
return false;
+ }
}
if (!string.IsNullOrEmpty(profileInfo.Manufacturer))
{
if (deviceInfo.Manufacturer == null || !IsRegexMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer))
+ {
return false;
+ }
}
if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl))
{
if (deviceInfo.ManufacturerUrl == null || !IsRegexMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl))
+ {
return false;
+ }
}
if (!string.IsNullOrEmpty(profileInfo.ModelDescription))
{
if (deviceInfo.ModelDescription == null || !IsRegexMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription))
+ {
return false;
+ }
}
if (!string.IsNullOrEmpty(profileInfo.ModelName))
{
if (deviceInfo.ModelName == null || !IsRegexMatch(deviceInfo.ModelName, profileInfo.ModelName))
+ {
return false;
+ }
}
if (!string.IsNullOrEmpty(profileInfo.ModelNumber))
{
if (deviceInfo.ModelNumber == null || !IsRegexMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber))
+ {
return false;
+ }
}
if (!string.IsNullOrEmpty(profileInfo.ModelUrl))
{
if (deviceInfo.ModelUrl == null || !IsRegexMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl))
+ {
return false;
+ }
}
if (!string.IsNullOrEmpty(profileInfo.SerialNumber))
{
if (deviceInfo.SerialNumber == null || !IsRegexMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber))
+ {
return false;
+ }
}
return true;
@@ -251,7 +268,7 @@ namespace Emby.Dlna
return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase);
case HeaderMatchType.Substring:
var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
- //_logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch);
+ // _logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch);
return isMatch;
case HeaderMatchType.Regex:
return Regex.IsMatch(value, header.Value, RegexOptions.IgnoreCase);
@@ -439,6 +456,7 @@ namespace Emby.Dlna
{
throw new ArgumentException("Profile is missing Id");
}
+
if (string.IsNullOrEmpty(profile.Name))
{
throw new ArgumentException("Profile is missing Name");
@@ -464,6 +482,7 @@ namespace Emby.Dlna
{
_profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
}
+
SerializeToXml(profile, path);
}
@@ -474,7 +493,7 @@ namespace Emby.Dlna
/// <summary>
/// Recreates the object using serialization, to ensure it's not a subclass.
- /// If it's a subclass it may not serlialize properly to xml (different root element tag name)
+ /// If it's a subclass it may not serlialize properly to xml (different root element tag name).
/// </summary>
/// <param name="profile"></param>
/// <returns></returns>
@@ -493,6 +512,7 @@ namespace Emby.Dlna
class InternalProfileInfo
{
internal DeviceProfileInfo Info { get; set; }
+
internal string Path { get; set; }
}
@@ -566,9 +586,9 @@ namespace Emby.Dlna
new Foobar2000Profile(),
new SharpSmartTvProfile(),
new MediaMonkeyProfile(),
- //new Windows81Profile(),
- //new WindowsMediaCenterProfile(),
- //new WindowsPhoneProfile(),
+ // new Windows81Profile(),
+ // new WindowsMediaCenterProfile(),
+ // new WindowsPhoneProfile(),
new DirectTvProfile(),
new DishHopperJoeyProfile(),
new DefaultProfile(),
diff --git a/Emby.Dlna/Eventing/EventManager.cs b/Emby.Dlna/Eventing/EventManager.cs
index efbb53b64..56c90c8b3 100644
--- a/Emby.Dlna/Eventing/EventManager.cs
+++ b/Emby.Dlna/Eventing/EventManager.cs
@@ -31,18 +31,26 @@ namespace Emby.Dlna.Eventing
public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl)
{
var subscription = GetSubscription(subscriptionId, false);
+ if (subscription != null)
+ {
+ subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300;
+ int timeoutSeconds = subscription.TimeoutSeconds;
+ subscription.SubscriptionTime = DateTime.UtcNow;
- subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300;
- int timeoutSeconds = subscription.TimeoutSeconds;
- subscription.SubscriptionTime = DateTime.UtcNow;
+ _logger.LogDebug(
+ "Renewing event subscription for {0} with timeout of {1} to {2}",
+ subscription.NotificationType,
+ timeoutSeconds,
+ subscription.CallbackUrl);
- _logger.LogDebug(
- "Renewing event subscription for {0} with timeout of {1} to {2}",
- subscription.NotificationType,
- timeoutSeconds,
- subscription.CallbackUrl);
+ return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds);
+ }
- return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds);
+ return new EventSubscriptionResponse
+ {
+ Content = string.Empty,
+ ContentType = "text/plain"
+ };
}
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
@@ -150,6 +158,7 @@ namespace Emby.Dlna.Eventing
builder.Append("</" + key + ">");
builder.Append("</e:property>");
}
+
builder.Append("</e:propertyset>");
var options = new HttpRequestOptions
@@ -169,7 +178,6 @@ namespace Emby.Dlna.Eventing
{
using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false))
{
-
}
}
catch (OperationCanceledException)
diff --git a/Emby.Dlna/Eventing/EventSubscription.cs b/Emby.Dlna/Eventing/EventSubscription.cs
index 51eaee9d7..40d73ee0e 100644
--- a/Emby.Dlna/Eventing/EventSubscription.cs
+++ b/Emby.Dlna/Eventing/EventSubscription.cs
@@ -7,10 +7,13 @@ namespace Emby.Dlna.Eventing
public class EventSubscription
{
public string Id { get; set; }
+
public string CallbackUrl { get; set; }
+
public string NotificationType { get; set; }
public DateTime SubscriptionTime { get; set; }
+
public int TimeoutSeconds { get; set; }
public long TriggerCount { get; set; }
diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs
index 47b235e59..9b9b57e97 100644
--- a/Emby.Dlna/Main/DlnaEntryPoint.cs
+++ b/Emby.Dlna/Main/DlnaEntryPoint.cs
@@ -35,8 +35,6 @@ namespace Emby.Dlna.Main
private readonly IServerConfigurationManager _config;
private readonly ILogger<DlnaEntryPoint> _logger;
private readonly IServerApplicationHost _appHost;
-
- private PlayToManager _manager;
private readonly ISessionManager _sessionManager;
private readonly IHttpClient _httpClient;
private readonly ILibraryManager _libraryManager;
@@ -47,14 +45,13 @@ namespace Emby.Dlna.Main
private readonly ILocalizationManager _localization;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IMediaEncoder _mediaEncoder;
-
private readonly IDeviceDiscovery _deviceDiscovery;
-
- private SsdpDevicePublisher _Publisher;
-
private readonly ISocketFactory _socketFactory;
private readonly INetworkManager _networkManager;
+ private readonly object _syncLock = new object();
+ private PlayToManager _manager;
+ private SsdpDevicePublisher _publisher;
private ISsdpCommunicationsServer _communicationsServer;
internal IContentDirectory ContentDirectory { get; private set; }
@@ -181,7 +178,7 @@ namespace Emby.Dlna.Main
var enableMultiSocketBinding = OperatingSystem.Id == OperatingSystemId.Windows ||
OperatingSystem.Id == OperatingSystemId.Linux;
- _communicationsServer = new SsdpCommunicationsServer(_config, _socketFactory, _networkManager, _logger, enableMultiSocketBinding)
+ _communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding)
{
IsShared = true
};
@@ -232,20 +229,22 @@ namespace Emby.Dlna.Main
return;
}
- if (_Publisher != null)
+ if (_publisher != null)
{
return;
}
try
{
- _Publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, OperatingSystem.Name, Environment.OSVersion.VersionString, _config.GetDlnaConfiguration().SendOnlyMatchedHost);
- _Publisher.LogFunction = LogMessage;
- _Publisher.SupportPnpRootDevice = false;
+ _publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, OperatingSystem.Name, Environment.OSVersion.VersionString, _config.GetDlnaConfiguration().SendOnlyMatchedHost)
+ {
+ LogFunction = LogMessage,
+ SupportPnpRootDevice = false
+ };
await RegisterServerEndpoints().ConfigureAwait(false);
- _Publisher.StartBroadcastingAliveMessages(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds));
+ _publisher.StartBroadcastingAliveMessages(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds));
}
catch (Exception ex)
{
@@ -267,6 +266,12 @@ namespace Emby.Dlna.Main
continue;
}
+ // Limit to LAN addresses only
+ if (!_networkManager.IsAddressInSubnets(address, true, true))
+ {
+ continue;
+ }
+
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address);
@@ -276,7 +281,7 @@ namespace Emby.Dlna.Main
var device = new SsdpRootDevice
{
- CacheLifetime = TimeSpan.FromSeconds(1800), //How long SSDP clients can cache this info.
+ CacheLifetime = TimeSpan.FromSeconds(1800), // How long SSDP clients can cache this info.
Location = uri, // Must point to the URL that serves your devices UPnP description document.
Address = address,
SubnetMask = _networkManager.GetLocalIpSubnetMask(address),
@@ -288,13 +293,13 @@ namespace Emby.Dlna.Main
};
SetProperies(device, fullService);
- _Publisher.AddDevice(device);
+ _publisher.AddDevice(device);
var embeddedDevices = new[]
{
"urn:schemas-upnp-org:service:ContentDirectory:1",
"urn:schemas-upnp-org:service:ConnectionManager:1",
- //"urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"
+ // "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"
};
foreach (var subDevice in embeddedDevices)
@@ -320,12 +325,13 @@ namespace Emby.Dlna.Main
{
guid = text.GetMD5();
}
+
return guid.ToString("N", CultureInfo.InvariantCulture);
}
private void SetProperies(SsdpDevice device, string fullDeviceType)
{
- var service = fullDeviceType.Replace("urn:", string.Empty).Replace(":1", string.Empty);
+ var service = fullDeviceType.Replace("urn:", string.Empty, StringComparison.OrdinalIgnoreCase).Replace(":1", string.Empty, StringComparison.OrdinalIgnoreCase);
var serviceParts = service.Split(':');
@@ -336,7 +342,6 @@ namespace Emby.Dlna.Main
device.DeviceType = serviceParts[2];
}
- private readonly object _syncLock = new object();
private void StartPlayToManager()
{
lock (_syncLock)
@@ -388,6 +393,7 @@ namespace Emby.Dlna.Main
{
_logger.LogError(ex, "Error disposing PlayTo manager");
}
+
_manager = null;
}
}
@@ -414,11 +420,11 @@ namespace Emby.Dlna.Main
public void DisposeDevicePublisher()
{
- if (_Publisher != null)
+ if (_publisher != null)
{
_logger.LogInformation("Disposing SsdpDevicePublisher");
- _Publisher.Dispose();
- _Publisher = null;
+ _publisher.Dispose();
+ _publisher = null;
}
}
}
diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs
index c7431d143..c5080b90f 100644
--- a/Emby.Dlna/PlayTo/Device.cs
+++ b/Emby.Dlna/PlayTo/Device.cs
@@ -19,8 +19,6 @@ namespace Emby.Dlna.PlayTo
{
public class Device : IDisposable
{
- #region Fields & Properties
-
private Timer _timer;
public DeviceInfo Properties { get; set; }
@@ -37,6 +35,7 @@ namespace Emby.Dlna.PlayTo
RefreshVolumeIfNeeded().GetAwaiter().GetResult();
return _volume;
}
+
set => _volume = value;
}
@@ -52,10 +51,10 @@ namespace Emby.Dlna.PlayTo
public bool IsStopped => TransportState == TRANSPORTSTATE.STOPPED;
- #endregion
-
private readonly IHttpClient _httpClient;
+
private readonly ILogger _logger;
+
private readonly IServerConfigurationManager _config;
public Action OnDeviceUnavailable { get; set; }
@@ -141,8 +140,6 @@ namespace Emby.Dlna.PlayTo
}
}
- #region Commanding
-
public Task VolumeDown(CancellationToken cancellationToken)
{
var sendVolume = Math.Max(Volume - 5, 0);
@@ -211,7 +208,9 @@ namespace Emby.Dlna.PlayTo
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetMute");
if (command == null)
+ {
return false;
+ }
var service = GetServiceRenderingControl();
@@ -232,7 +231,7 @@ namespace Emby.Dlna.PlayTo
}
/// <summary>
- /// Sets volume on a scale of 0-100
+ /// Sets volume on a scale of 0-100.
/// </summary>
public async Task SetVolume(int value, CancellationToken cancellationToken)
{
@@ -240,7 +239,9 @@ namespace Emby.Dlna.PlayTo
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
if (command == null)
+ {
return;
+ }
var service = GetServiceRenderingControl();
@@ -263,7 +264,9 @@ namespace Emby.Dlna.PlayTo
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
if (command == null)
+ {
return;
+ }
var service = GetAvTransportService();
@@ -288,7 +291,9 @@ namespace Emby.Dlna.PlayTo
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
if (command == null)
+ {
return;
+ }
var dictionary = new Dictionary<string, string>
{
@@ -401,11 +406,8 @@ namespace Emby.Dlna.PlayTo
RestartTimer(true);
}
- #endregion
-
- #region Get data
-
private int _connectFailureCount;
+
private async void TimerCallback(object sender)
{
if (_disposed)
@@ -458,7 +460,9 @@ namespace Emby.Dlna.PlayTo
_connectFailureCount = 0;
if (_disposed)
+ {
return;
+ }
// If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive
if (transportState.Value == TRANSPORTSTATE.STOPPED)
@@ -478,7 +482,9 @@ namespace Emby.Dlna.PlayTo
catch (Exception ex)
{
if (_disposed)
+ {
return;
+ }
_logger.LogError(ex, "Error updating device info for {DeviceName}", Properties.Name);
@@ -494,6 +500,7 @@ namespace Emby.Dlna.PlayTo
return;
}
}
+
RestartTimerInactive();
}
}
@@ -578,7 +585,9 @@ namespace Emby.Dlna.PlayTo
cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null)
+ {
return;
+ }
var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse")
.Select(i => i.Element("CurrentMute"))
@@ -750,7 +759,7 @@ namespace Emby.Dlna.PlayTo
if (track == null)
{
- //If track is null, some vendors do this, use GetMediaInfo instead
+ // If track is null, some vendors do this, use GetMediaInfo instead
return (true, null);
}
@@ -794,7 +803,6 @@ namespace Emby.Dlna.PlayTo
}
catch (XmlException)
{
-
}
// first try to add a root node with a dlna namesapce
@@ -806,7 +814,6 @@ namespace Emby.Dlna.PlayTo
}
catch (XmlException)
{
-
}
// some devices send back invalid xml
@@ -816,7 +823,6 @@ namespace Emby.Dlna.PlayTo
}
catch (XmlException)
{
-
}
return null;
@@ -871,10 +877,6 @@ namespace Emby.Dlna.PlayTo
return new string[4];
}
- #endregion
-
- #region From XML
-
private async Task<TransportCommands> GetAVProtocolAsync(CancellationToken cancellationToken)
{
if (AvCommands != null)
@@ -1069,8 +1071,6 @@ namespace Emby.Dlna.PlayTo
return new Device(deviceProperties, httpClient, logger, config);
}
- #endregion
-
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
private static DeviceIcon CreateIcon(XElement element)
{
@@ -1194,8 +1194,6 @@ namespace Emby.Dlna.PlayTo
});
}
- #region IDisposable
-
bool _disposed;
public void Dispose()
@@ -1222,8 +1220,6 @@ namespace Emby.Dlna.PlayTo
_disposed = true;
}
- #endregion
-
public override string ToString()
{
return string.Format("{0} - {1}", Properties.Name, Properties.BaseUrl);
diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs
index f1c69196a..92a93d434 100644
--- a/Emby.Dlna/PlayTo/PlayToController.cs
+++ b/Emby.Dlna/PlayTo/PlayToController.cs
@@ -425,6 +425,7 @@ namespace Emby.Dlna.PlayTo
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
return;
}
+
await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false);
}
}
@@ -713,6 +714,7 @@ namespace Emby.Dlna.PlayTo
throw new ArgumentException("Volume argument cannot be null");
}
+
default:
return Task.CompletedTask;
}
@@ -798,12 +800,15 @@ namespace Emby.Dlna.PlayTo
public int? SubtitleStreamIndex { get; set; }
public string DeviceProfileId { get; set; }
+
public string DeviceId { get; set; }
public string MediaSourceId { get; set; }
+
public string LiveStreamId { get; set; }
public BaseItem Item { get; set; }
+
private MediaSourceInfo MediaSource;
private IMediaSourceManager _mediaSourceManager;
diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs
index 9b0339e5d..512589e4d 100644
--- a/Emby.Dlna/PlayTo/PlayToManager.cs
+++ b/Emby.Dlna/PlayTo/PlayToManager.cs
@@ -78,9 +78,15 @@ namespace Emby.Dlna.PlayTo
var info = e.Argument;
- if (!info.Headers.TryGetValue("USN", out string usn)) usn = string.Empty;
+ if (!info.Headers.TryGetValue("USN", out string usn))
+ {
+ usn = string.Empty;
+ }
- if (!info.Headers.TryGetValue("NT", out string nt)) nt = string.Empty;
+ if (!info.Headers.TryGetValue("NT", out string nt))
+ {
+ nt = string.Empty;
+ }
string location = info.Location.ToString();
@@ -88,7 +94,7 @@ namespace Emby.Dlna.PlayTo
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 &&
nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1)
{
- //_logger.LogDebug("Upnp device {0} does not contain a MediaRenderer device (0).", location);
+ // _logger.LogDebug("Upnp device {0} does not contain a MediaRenderer device (0).", location);
return;
}
@@ -112,7 +118,6 @@ namespace Emby.Dlna.PlayTo
}
catch (OperationCanceledException)
{
-
}
catch (Exception ex)
{
@@ -133,6 +138,7 @@ namespace Emby.Dlna.PlayTo
usn = usn.Substring(index);
found = true;
}
+
index = usn.IndexOf("::", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
@@ -243,7 +249,6 @@ namespace Emby.Dlna.PlayTo
}
catch
{
-
}
_sessionLock.Dispose();
diff --git a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs
index 3b169e599..fa42b80e8 100644
--- a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs
+++ b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs
@@ -12,6 +12,7 @@ namespace Emby.Dlna.PlayTo
public class MediaChangedEventArgs : EventArgs
{
public uBaseObject OldMediaInfo { get; set; }
+
public uBaseObject NewMediaInfo { get; set; }
}
}
diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
index 8c1362007..ab262bebf 100644
--- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs
+++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
@@ -91,7 +91,6 @@ namespace Emby.Dlna.PlayTo
using (await _httpClient.SendAsync(options, new HttpMethod("SUBSCRIBE")).ConfigureAwait(false))
{
-
}
}
diff --git a/Emby.Dlna/PlayTo/uBaseObject.cs b/Emby.Dlna/PlayTo/uBaseObject.cs
index a8ed5692c..05c19299f 100644
--- a/Emby.Dlna/PlayTo/uBaseObject.cs
+++ b/Emby.Dlna/PlayTo/uBaseObject.cs
@@ -44,10 +44,12 @@ namespace Emby.Dlna.PlayTo
{
return MediaBrowser.Model.Entities.MediaType.Audio;
}
+
if (classType.IndexOf(MediaBrowser.Model.Entities.MediaType.Video, StringComparison.Ordinal) != -1)
{
return MediaBrowser.Model.Entities.MediaType.Video;
}
+
if (classType.IndexOf("image", StringComparison.Ordinal) != -1)
{
return MediaBrowser.Model.Entities.MediaType.Photo;
diff --git a/Emby.Dlna/Profiles/DefaultProfile.cs b/Emby.Dlna/Profiles/DefaultProfile.cs
index 2347ebd0d..90a23a4a2 100644
--- a/Emby.Dlna/Profiles/DefaultProfile.cs
+++ b/Emby.Dlna/Profiles/DefaultProfile.cs
@@ -164,7 +164,7 @@ namespace Emby.Dlna.Profiles
public void AddXmlRootAttribute(string name, string value)
{
- var atts = XmlRootAttributes ?? new XmlAttribute[] { };
+ var atts = XmlRootAttributes ?? System.Array.Empty<XmlAttribute>();
var list = atts.ToList();
list.Add(new XmlAttribute
diff --git a/Emby.Dlna/Profiles/DenonAvrProfile.cs b/Emby.Dlna/Profiles/DenonAvrProfile.cs
index 73a87c499..a5ba0f36c 100644
--- a/Emby.Dlna/Profiles/DenonAvrProfile.cs
+++ b/Emby.Dlna/Profiles/DenonAvrProfile.cs
@@ -28,7 +28,7 @@ namespace Emby.Dlna.Profiles
},
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = System.Array.Empty<ResponseProfile>();
}
}
}
diff --git a/Emby.Dlna/Profiles/DirectTvProfile.cs b/Emby.Dlna/Profiles/DirectTvProfile.cs
index 5ca388167..f6f98b07d 100644
--- a/Emby.Dlna/Profiles/DirectTvProfile.cs
+++ b/Emby.Dlna/Profiles/DirectTvProfile.cs
@@ -123,7 +123,7 @@ namespace Emby.Dlna.Profiles
}
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = System.Array.Empty<ResponseProfile>();
}
}
}
diff --git a/Emby.Dlna/Profiles/Foobar2000Profile.cs b/Emby.Dlna/Profiles/Foobar2000Profile.cs
index ea3de686a..68e959770 100644
--- a/Emby.Dlna/Profiles/Foobar2000Profile.cs
+++ b/Emby.Dlna/Profiles/Foobar2000Profile.cs
@@ -72,7 +72,7 @@ namespace Emby.Dlna.Profiles
}
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = System.Array.Empty<ResponseProfile>();
}
}
}
diff --git a/Emby.Dlna/Profiles/MarantzProfile.cs b/Emby.Dlna/Profiles/MarantzProfile.cs
index 6cfcc3b82..24078ab29 100644
--- a/Emby.Dlna/Profiles/MarantzProfile.cs
+++ b/Emby.Dlna/Profiles/MarantzProfile.cs
@@ -37,7 +37,7 @@ namespace Emby.Dlna.Profiles
},
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = System.Array.Empty<ResponseProfile>();
}
}
}
diff --git a/Emby.Dlna/Profiles/MediaMonkeyProfile.cs b/Emby.Dlna/Profiles/MediaMonkeyProfile.cs
index 7161af738..28d639b6d 100644
--- a/Emby.Dlna/Profiles/MediaMonkeyProfile.cs
+++ b/Emby.Dlna/Profiles/MediaMonkeyProfile.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System;
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
@@ -37,7 +38,7 @@ namespace Emby.Dlna.Profiles
}
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = Array.Empty<ResponseProfile>();
}
}
}
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs
index 42b066d52..238fe9f6b 100644
--- a/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs
+++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System;
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
@@ -223,7 +224,7 @@ namespace Emby.Dlna.Profiles
}
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = Array.Empty<ResponseProfile>();
}
}
}
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs
index fbdf2c18e..812a48151 100644
--- a/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs
+++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System;
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
@@ -223,7 +224,7 @@ namespace Emby.Dlna.Profiles
}
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = Array.Empty<ResponseProfile>();
}
}
}
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs
index ce32179a1..6bfff322e 100644
--- a/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs
+++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System;
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
@@ -211,7 +212,7 @@ namespace Emby.Dlna.Profiles
}
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = Array.Empty<ResponseProfile>();
}
}
}
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs
index aa1721d39..ec2529574 100644
--- a/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs
+++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System;
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
@@ -211,7 +212,7 @@ namespace Emby.Dlna.Profiles
}
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = Array.Empty<ResponseProfile>();
}
}
}
diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs
index 5ecc81a2f..7143c3109 100644
--- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs
+++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs
@@ -134,6 +134,7 @@ namespace Emby.Dlna.Server
return result;
}
}
+
return c.ToString(CultureInfo.InvariantCulture);
}
@@ -157,18 +158,22 @@ namespace Emby.Dlna.Server
{
break;
}
+
if (stringBuilder == null)
{
stringBuilder = new StringBuilder();
}
+
stringBuilder.Append(str, num, num2 - num);
stringBuilder.Append(GetEscapeSequence(str[num2]));
num = num2 + 1;
}
+
if (stringBuilder == null)
{
return str;
}
+
stringBuilder.Append(str, num, length - num);
return stringBuilder.ToString();
}
diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs
index 161a3434c..699d325ea 100644
--- a/Emby.Dlna/Service/BaseControlHandler.cs
+++ b/Emby.Dlna/Service/BaseControlHandler.cs
@@ -18,6 +18,7 @@ namespace Emby.Dlna.Service
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
protected IServerConfigurationManager Config { get; }
+
protected ILogger Logger { get; }
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
@@ -135,6 +136,7 @@ namespace Emby.Dlna.Service
break;
}
+
default:
{
await reader.SkipAsync().ConfigureAwait(false);
@@ -211,7 +213,9 @@ namespace Emby.Dlna.Service
private class ControlRequestInfo
{
public string LocalName { get; set; }
+
public string NamespaceURI { get; set; }
+
public Dictionary<string, string> Headers { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
diff --git a/Emby.Dlna/Service/ServiceXmlBuilder.cs b/Emby.Dlna/Service/ServiceXmlBuilder.cs
index 62ffd9e42..af557aa14 100644
--- a/Emby.Dlna/Service/ServiceXmlBuilder.cs
+++ b/Emby.Dlna/Service/ServiceXmlBuilder.cs
@@ -80,6 +80,7 @@ namespace Emby.Dlna.Service
{
builder.Append("<allowedValue>" + DescriptionXmlBuilder.Escape(allowedValue) + "</allowedValue>");
}
+
builder.Append("</allowedValueList>");
}
diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs
index ab5e56ab0..7daac96d1 100644
--- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs
+++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs
@@ -77,7 +77,7 @@ namespace Emby.Dlna.Ssdp
// (Optional) Set the filter so we only see notifications for devices we care about
// (can be any search target value i.e device type, uuid value etc - any value that appears in the
// DiscoverdSsdpDevice.NotificationType property or that is used with the searchTarget parameter of the Search method).
- //_DeviceLocator.NotificationFilter = "upnp:rootdevice";
+ // _DeviceLocator.NotificationFilter = "upnp:rootdevice";
// Connect our event handler so we process devices as they are found
_deviceLocator.DeviceAvailable += OnDeviceLocatorDeviceAvailable;
diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs
index 5494df9d6..3c874c62c 100644
--- a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs
+++ b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs
@@ -64,6 +64,7 @@ namespace Emby.Naming.AudioBook
{
result.ChapterNumber = int.Parse(matches[0].Groups[0].Value);
}
+
if (matches.Count > 1)
{
result.PartNumber = int.Parse(matches[matches.Count - 1].Groups[0].Value);
diff --git a/Emby.Naming/Common/MediaType.cs b/Emby.Naming/Common/MediaType.cs
index cc18ce4cd..148833765 100644
--- a/Emby.Naming/Common/MediaType.cs
+++ b/Emby.Naming/Common/MediaType.cs
@@ -5,17 +5,17 @@ namespace Emby.Naming.Common
public enum MediaType
{
/// <summary>
- /// The audio
+ /// The audio.
/// </summary>
Audio = 0,
/// <summary>
- /// The photo
+ /// The photo.
/// </summary>
Photo = 1,
/// <summary>
- /// The video
+ /// The video.
/// </summary>
Video = 2
}
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 25ee7e9ec..17208d97d 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -43,9 +43,9 @@ using Emby.Server.Implementations.Security;
using Emby.Server.Implementations.Serialization;
using Emby.Server.Implementations.Services;
using Emby.Server.Implementations.Session;
+using Emby.Server.Implementations.SyncPlay;
using Emby.Server.Implementations.TV;
using Emby.Server.Implementations.Updates;
-using Emby.Server.Implementations.SyncPlay;
using MediaBrowser.Api;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
@@ -78,8 +78,8 @@ using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Controller.Subtitles;
-using MediaBrowser.Controller.TV;
using MediaBrowser.Controller.SyncPlay;
+using MediaBrowser.Controller.TV;
using MediaBrowser.LocalMetadata.Savers;
using MediaBrowser.MediaEncoding.BdInfo;
using MediaBrowser.Model.Configuration;
@@ -483,12 +483,10 @@ namespace Emby.Server.Implementations
foreach (var plugin in Plugins)
{
- pluginBuilder.AppendLine(
- string.Format(
- CultureInfo.InvariantCulture,
- "{0} {1}",
- plugin.Name,
- plugin.Version));
+ pluginBuilder.Append(plugin.Name)
+ .Append(' ')
+ .Append(plugin.Version)
+ .AppendLine();
}
Logger.LogInformation("Plugins: {Plugins}", pluginBuilder.ToString());
@@ -565,10 +563,8 @@ namespace Emby.Server.Implementations
serviceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>));
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
- // TODO: Add StartupOptions.FFmpegPath to IConfiguration and remove this custom activation
serviceCollection.AddTransient(provider => new Lazy<EncodingHelper>(provider.GetRequiredService<EncodingHelper>));
- serviceCollection.AddSingleton<IMediaEncoder>(provider =>
- ActivatorUtilities.CreateInstance<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(provider, _startupOptions.FFmpegPath ?? string.Empty));
+ serviceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
// TODO: Refactor to eliminate the circular dependencies here so that Lazy<T> isn't required
serviceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>));
@@ -872,6 +868,11 @@ namespace Emby.Server.Implementations
Logger.LogError(ex, "Error getting exported types from {Assembly}", ass.FullName);
continue;
}
+ catch (TypeLoadException ex)
+ {
+ Logger.LogError(ex, "Error loading types from {Assembly}.", ass.FullName);
+ continue;
+ }
foreach (Type type in exportedTypes)
{
@@ -955,7 +956,7 @@ namespace Emby.Server.Implementations
}
/// <summary>
- /// Notifies that the kernel that a change has been made that requires a restart
+ /// Notifies that the kernel that a change has been made that requires a restart.
/// </summary>
public void NotifyPendingRestart()
{
@@ -1151,7 +1152,7 @@ namespace Emby.Server.Implementations
return null;
}
- return GetLocalApiUrl(addresses.First());
+ return GetLocalApiUrl(addresses[0]);
}
catch (Exception ex)
{
@@ -1224,13 +1225,13 @@ namespace Emby.Server.Implementations
var addresses = ServerConfigurationManager
.Configuration
.LocalNetworkAddresses
- .Select(NormalizeConfiguredLocalAddress)
+ .Select(x => NormalizeConfiguredLocalAddress(x))
.Where(i => i != null)
.ToList();
if (addresses.Count == 0)
{
- addresses.AddRange(_networkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces));
+ addresses.AddRange(_networkManager.GetLocalIpAddresses());
}
var resultList = new List<IPAddress>();
@@ -1245,8 +1246,7 @@ namespace Emby.Server.Implementations
}
}
- var valid = await IsLocalIpAddressValidAsync(address, cancellationToken).ConfigureAwait(false);
- if (valid)
+ if (await IsLocalIpAddressValidAsync(address, cancellationToken).ConfigureAwait(false))
{
resultList.Add(address);
@@ -1260,13 +1260,12 @@ namespace Emby.Server.Implementations
return resultList;
}
- public IPAddress NormalizeConfiguredLocalAddress(string address)
+ public IPAddress NormalizeConfiguredLocalAddress(ReadOnlySpan<char> address)
{
var index = address.Trim('/').IndexOf('/');
-
if (index != -1)
{
- address = address.Substring(index + 1);
+ address = address.Slice(index + 1);
}
if (IPAddress.TryParse(address.Trim('/'), out IPAddress result))
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 519c5bbef..c803d9d82 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -1072,7 +1072,7 @@ namespace Emby.Server.Implementations.Channels
}
// was used for status
- //if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal))
+ // if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal))
//{
// item.ExternalEtag = info.Etag;
// forceUpdate = true;
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index 8fb9520d6..ac2edc1e2 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -363,60 +363,4 @@ namespace Emby.Server.Implementations.Collections
return results.Values;
}
}
-
- /// <summary>
- /// The collection manager entry point.
- /// </summary>
- public sealed class CollectionManagerEntryPoint : IServerEntryPoint
- {
- private readonly CollectionManager _collectionManager;
- private readonly IServerConfigurationManager _config;
- private readonly ILogger<CollectionManagerEntryPoint> _logger;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="CollectionManagerEntryPoint"/> class.
- /// </summary>
- /// <param name="collectionManager">The collection manager.</param>
- /// <param name="config">The server configuration manager.</param>
- /// <param name="logger">The logger.</param>
- public CollectionManagerEntryPoint(
- ICollectionManager collectionManager,
- IServerConfigurationManager config,
- ILogger<CollectionManagerEntryPoint> logger)
- {
- _collectionManager = (CollectionManager)collectionManager;
- _config = config;
- _logger = logger;
- }
-
- /// <inheritdoc />
- public async Task RunAsync()
- {
- if (!_config.Configuration.CollectionsUpgraded && _config.Configuration.IsStartupWizardCompleted)
- {
- var path = _collectionManager.GetCollectionsFolderPath();
-
- if (Directory.Exists(path))
- {
- try
- {
- await _collectionManager.EnsureLibraryFolder(path, true).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error creating camera uploads library");
- }
-
- _config.Configuration.CollectionsUpgraded = true;
- _config.SaveConfiguration();
- }
- }
- }
-
- /// <inheritdoc />
- public void Dispose()
- {
- // Nothing to dispose
- }
- }
}
diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
index 94ee1ced7..a15295fca 100644
--- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
+++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
@@ -109,7 +109,6 @@ namespace Emby.Server.Implementations.Configuration
if (!string.IsNullOrWhiteSpace(newPath)
&& !string.Equals(Configuration.CertificatePath, newPath, StringComparison.Ordinal))
{
- // Validate
if (!File.Exists(newPath))
{
throw new FileNotFoundException(
@@ -133,7 +132,6 @@ namespace Emby.Server.Implementations.Configuration
if (!string.IsNullOrWhiteSpace(newPath)
&& !string.Equals(Configuration.MetadataPath, newPath, StringComparison.Ordinal))
{
- // Validate
if (!Directory.Exists(newPath))
{
throw new DirectoryNotFoundException(
@@ -146,60 +144,5 @@ namespace Emby.Server.Implementations.Configuration
EnsureWriteAccess(newPath);
}
}
-
- /// <summary>
- /// Sets all configuration values to their optimal values.
- /// </summary>
- /// <returns>If the configuration changed.</returns>
- public bool SetOptimalValues()
- {
- var config = Configuration;
-
- var changed = false;
-
- if (!config.EnableCaseSensitiveItemIds)
- {
- config.EnableCaseSensitiveItemIds = true;
- changed = true;
- }
-
- if (!config.SkipDeserializationForBasicTypes)
- {
- config.SkipDeserializationForBasicTypes = true;
- changed = true;
- }
-
- if (!config.EnableSimpleArtistDetection)
- {
- config.EnableSimpleArtistDetection = true;
- changed = true;
- }
-
- if (!config.EnableNormalizedItemByNameIds)
- {
- config.EnableNormalizedItemByNameIds = true;
- changed = true;
- }
-
- if (!config.DisableLiveTvChannelUserDataName)
- {
- config.DisableLiveTvChannelUserDataName = true;
- changed = true;
- }
-
- if (!config.EnableNewOmdbSupport)
- {
- config.EnableNewOmdbSupport = true;
- changed = true;
- }
-
- if (!config.CollectionsUpgraded)
- {
- config.CollectionsUpgraded = true;
- changed = true;
- }
-
- return changed;
- }
}
}
diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs
index dea9b6682..ff7ee085f 100644
--- a/Emby.Server.Implementations/ConfigurationOptions.cs
+++ b/Emby.Server.Implementations/ConfigurationOptions.cs
@@ -17,7 +17,6 @@ namespace Emby.Server.Implementations
{
{ HostWebClientKey, bool.TrueString },
{ HttpListenerHost.DefaultRedirectKey, "web/index.html" },
- { InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest-stable.json" },
{ FfmpegProbeSizeKey, "1G" },
{ FfmpegAnalyzeDurationKey, "200M" },
{ PlaylistsAllowDuplicatesKey, bool.TrueString }
diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
index f816fd54f..8a3716380 100644
--- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
+++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
@@ -162,7 +162,6 @@ namespace Emby.Server.Implementations.Data
}
return false;
-
}, ReadTransactionMode);
}
@@ -248,12 +247,12 @@ namespace Emby.Server.Implementations.Data
public enum SynchronousMode
{
/// <summary>
- /// SQLite continues without syncing as soon as it has handed data off to the operating system
+ /// SQLite continues without syncing as soon as it has handed data off to the operating system.
/// </summary>
Off = 0,
/// <summary>
- /// SQLite database engine will still sync at the most critical moments
+ /// SQLite database engine will still sync at the most critical moments.
/// </summary>
Normal = 1,
diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
index 6c9bcff0f..3de9d6371 100644
--- a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
+++ b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
@@ -51,7 +51,6 @@ namespace Emby.Server.Implementations.Data
_libraryManager.DeleteItem(item, new DeleteOptions
{
DeleteFileLocation = false
-
});
}
diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs
index 63d0321b7..5597155a8 100644
--- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs
@@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.Data
}
/// <summary>
- /// Opens the connection to the database
+ /// Opens the connection to the database.
/// </summary>
/// <returns>Task.</returns>
private void InitializeInternal()
@@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.Data
}
/// <summary>
- /// Save the display preferences associated with an item in the repo
+ /// Save the display preferences associated with an item in the repo.
/// </summary>
/// <param name="displayPreferences">The display preferences.</param>
/// <param name="userId">The user id.</param>
@@ -122,7 +122,7 @@ namespace Emby.Server.Implementations.Data
}
/// <summary>
- /// Save all display preferences associated with a user in the repo
+ /// Save all display preferences associated with a user in the repo.
/// </summary>
/// <param name="displayPreferences">The display preferences.</param>
/// <param name="userId">The user id.</param>
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index 43a593f11..a6390b1ef 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Data
protected override TempStoreMode TempStore => TempStoreMode.Memory;
/// <summary>
- /// Opens the connection to the database
+ /// Opens the connection to the database.
/// </summary>
public void Initialize(SqliteUserDataRepository userDataRepo, IUserManager userManager)
{
@@ -321,7 +321,6 @@ namespace Emby.Server.Implementations.Data
AddColumn(db, "MediaStreams", "ColorPrimaries", "TEXT", existingColumnNames);
AddColumn(db, "MediaStreams", "ColorSpace", "TEXT", existingColumnNames);
AddColumn(db, "MediaStreams", "ColorTransfer", "TEXT", existingColumnNames);
-
}, TransactionMode);
connection.RunQueries(postQueries);
@@ -549,7 +548,7 @@ namespace Emby.Server.Implementations.Data
}
/// <summary>
- /// Save a standard item in the repo
+ /// Save a standard item in the repo.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="cancellationToken">The cancellation token.</param>
@@ -794,6 +793,7 @@ namespace Emby.Server.Implementations.Data
{
saveItemStatement.TryBindNull("@Width");
}
+
if (item.Height > 0)
{
saveItemStatement.TryBind("@Height", item.Height);
@@ -933,6 +933,7 @@ namespace Emby.Server.Implementations.Data
{
saveItemStatement.TryBindNull("@SeriesName");
}
+
if (string.IsNullOrWhiteSpace(userDataKey))
{
saveItemStatement.TryBindNull("@UserDataKey");
@@ -1008,6 +1009,7 @@ namespace Emby.Server.Implementations.Data
{
artists = string.Join("|", hasArtists.Artists);
}
+
saveItemStatement.TryBind("@Artists", artists);
string albumArtists = null;
@@ -1107,6 +1109,7 @@ namespace Emby.Server.Implementations.Data
{
continue;
}
+
str.Append(ToValueString(i) + "|");
}
@@ -1205,7 +1208,7 @@ namespace Emby.Server.Implementations.Data
}
/// <summary>
- /// Internal retrieve from items or users table
+ /// Internal retrieve from items or users table.
/// </summary>
/// <param name="id">The id.</param>
/// <returns>BaseItem.</returns>
@@ -1367,6 +1370,7 @@ namespace Emby.Server.Implementations.Data
hasStartDate.StartDate = reader[index].ReadDateTime();
}
}
+
index++;
}
@@ -1374,12 +1378,14 @@ namespace Emby.Server.Implementations.Data
{
item.EndDate = reader[index].TryReadDateTime();
}
+
index++;
if (!reader.IsDBNull(index))
{
item.ChannelId = new Guid(reader.GetString(index));
}
+
index++;
if (enableProgramAttributes)
@@ -1390,24 +1396,28 @@ namespace Emby.Server.Implementations.Data
{
hasProgramAttributes.IsMovie = reader.GetBoolean(index);
}
+
index++;
if (!reader.IsDBNull(index))
{
hasProgramAttributes.IsSeries = reader.GetBoolean(index);
}
+
index++;
if (!reader.IsDBNull(index))
{
hasProgramAttributes.EpisodeTitle = reader.GetString(index);
}
+
index++;
if (!reader.IsDBNull(index))
{
hasProgramAttributes.IsRepeat = reader.GetBoolean(index);
}
+
index++;
}
else
@@ -1420,6 +1430,7 @@ namespace Emby.Server.Implementations.Data
{
item.CommunityRating = reader.GetFloat(index);
}
+
index++;
if (HasField(query, ItemFields.CustomRating))
@@ -1428,6 +1439,7 @@ namespace Emby.Server.Implementations.Data
{
item.CustomRating = reader.GetString(index);
}
+
index++;
}
@@ -1435,6 +1447,7 @@ namespace Emby.Server.Implementations.Data
{
item.IndexNumber = reader.GetInt32(index);
}
+
index++;
if (HasField(query, ItemFields.Settings))
@@ -1443,18 +1456,21 @@ namespace Emby.Server.Implementations.Data
{
item.IsLocked = reader.GetBoolean(index);
}
+
index++;
if (!reader.IsDBNull(index))
{
item.PreferredMetadataLanguage = reader.GetString(index);
}
+
index++;
if (!reader.IsDBNull(index))
{
item.PreferredMetadataCountryCode = reader.GetString(index);
}
+
index++;
}
@@ -1464,6 +1480,7 @@ namespace Emby.Server.Implementations.Data
{
item.Width = reader.GetInt32(index);
}
+
index++;
}
@@ -1473,6 +1490,7 @@ namespace Emby.Server.Implementations.Data
{
item.Height = reader.GetInt32(index);
}
+
index++;
}
@@ -1482,6 +1500,7 @@ namespace Emby.Server.Implementations.Data
{
item.DateLastRefreshed = reader[index].ReadDateTime();
}
+
index++;
}
@@ -1489,18 +1508,21 @@ namespace Emby.Server.Implementations.Data
{
item.Name = reader.GetString(index);
}
+
index++;
if (!reader.IsDBNull(index))
{
item.Path = RestorePath(reader.GetString(index));
}
+
index++;
if (!reader.IsDBNull(index))
{
item.PremiereDate = reader[index].TryReadDateTime();
}
+
index++;
if (HasField(query, ItemFields.Overview))
@@ -1509,6 +1531,7 @@ namespace Emby.Server.Implementations.Data
{
item.Overview = reader.GetString(index);
}
+
index++;
}
@@ -1516,18 +1539,21 @@ namespace Emby.Server.Implementations.Data
{
item.ParentIndexNumber = reader.GetInt32(index);
}
+
index++;
if (!reader.IsDBNull(index))
{
item.ProductionYear = reader.GetInt32(index);
}
+
index++;
if (!reader.IsDBNull(index))
{
item.OfficialRating = reader.GetString(index);
}
+
index++;
if (HasField(query, ItemFields.SortName))
@@ -1536,6 +1562,7 @@ namespace Emby.Server.Implementations.Data
{
item.ForcedSortName = reader.GetString(index);
}
+
index++;
}
@@ -1543,12 +1570,14 @@ namespace Emby.Server.Implementations.Data
{
item.RunTimeTicks = reader.GetInt64(index);
}
+
index++;
if (!reader.IsDBNull(index))
{
item.Size = reader.GetInt64(index);
}
+
index++;
if (HasField(query, ItemFields.DateCreated))
@@ -1557,6 +1586,7 @@ namespace Emby.Server.Implementations.Data
{
item.DateCreated = reader[index].ReadDateTime();
}
+
index++;
}
@@ -1564,6 +1594,7 @@ namespace Emby.Server.Implementations.Data
{
item.DateModified = reader[index].ReadDateTime();
}
+
index++;
item.Id = reader.GetGuid(index);
@@ -1575,6 +1606,7 @@ namespace Emby.Server.Implementations.Data
{
item.Genres = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
}
+
index++;
}
@@ -1582,6 +1614,7 @@ namespace Emby.Server.Implementations.Data
{
item.ParentId = reader.GetGuid(index);
}
+
index++;
if (!reader.IsDBNull(index))
@@ -1591,6 +1624,7 @@ namespace Emby.Server.Implementations.Data
item.Audio = audio;
}
}
+
index++;
// TODO: Even if not needed by apps, the server needs it internally
@@ -1604,6 +1638,7 @@ namespace Emby.Server.Implementations.Data
liveTvChannel.ServiceName = reader.GetString(index);
}
}
+
index++;
}
@@ -1611,6 +1646,7 @@ namespace Emby.Server.Implementations.Data
{
item.IsInMixedFolder = reader.GetBoolean(index);
}
+
index++;
if (HasField(query, ItemFields.DateLastSaved))
@@ -1619,6 +1655,7 @@ namespace Emby.Server.Implementations.Data
{
item.DateLastSaved = reader[index].ReadDateTime();
}
+
index++;
}
@@ -1636,8 +1673,10 @@ namespace Emby.Server.Implementations.Data
}
}
}
+
item.LockedFields = GetLockedFields(reader.GetString(index)).ToArray();
}
+
index++;
}
@@ -1647,6 +1686,7 @@ namespace Emby.Server.Implementations.Data
{
item.Studios = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
}
+
index++;
}
@@ -1656,6 +1696,7 @@ namespace Emby.Server.Implementations.Data
{
item.Tags = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
}
+
index++;
}
@@ -1675,9 +1716,11 @@ namespace Emby.Server.Implementations.Data
}
}
}
+
trailer.TrailerTypes = GetTrailerTypes(reader.GetString(index)).ToArray();
}
}
+
index++;
}
@@ -1687,6 +1730,7 @@ namespace Emby.Server.Implementations.Data
{
item.OriginalTitle = reader.GetString(index);
}
+
index++;
}
@@ -1697,6 +1741,7 @@ namespace Emby.Server.Implementations.Data
video.PrimaryVersionId = reader.GetString(index);
}
}
+
index++;
if (HasField(query, ItemFields.DateLastMediaAdded))
@@ -1705,6 +1750,7 @@ namespace Emby.Server.Implementations.Data
{
folder.DateLastMediaAdded = reader[index].TryReadDateTime();
}
+
index++;
}
@@ -1712,18 +1758,21 @@ namespace Emby.Server.Implementations.Data
{
item.Album = reader.GetString(index);
}
+
index++;
if (!reader.IsDBNull(index))
{
item.CriticRating = reader.GetFloat(index);
}
+
index++;
if (!reader.IsDBNull(index))
{
item.IsVirtualItem = reader.GetBoolean(index);
}
+
index++;
if (item is IHasSeries hasSeriesName)
@@ -1733,6 +1782,7 @@ namespace Emby.Server.Implementations.Data
hasSeriesName.SeriesName = reader.GetString(index);
}
}
+
index++;
if (hasEpisodeAttributes)
@@ -1743,6 +1793,7 @@ namespace Emby.Server.Implementations.Data
{
episode.SeasonName = reader.GetString(index);
}
+
index++;
if (!reader.IsDBNull(index))
{
@@ -1753,6 +1804,7 @@ namespace Emby.Server.Implementations.Data
{
index++;
}
+
index++;
}
@@ -1766,6 +1818,7 @@ namespace Emby.Server.Implementations.Data
hasSeries.SeriesId = reader.GetGuid(index);
}
}
+
index++;
}
@@ -1775,6 +1828,7 @@ namespace Emby.Server.Implementations.Data
{
item.PresentationUniqueKey = reader.GetString(index);
}
+
index++;
}
@@ -1784,6 +1838,7 @@ namespace Emby.Server.Implementations.Data
{
item.InheritedParentalRatingValue = reader.GetInt32(index);
}
+
index++;
}
@@ -1793,6 +1848,7 @@ namespace Emby.Server.Implementations.Data
{
item.ExternalSeriesId = reader.GetString(index);
}
+
index++;
}
@@ -1802,6 +1858,7 @@ namespace Emby.Server.Implementations.Data
{
item.Tagline = reader.GetString(index);
}
+
index++;
}
@@ -1809,6 +1866,7 @@ namespace Emby.Server.Implementations.Data
{
DeserializeProviderIds(reader.GetString(index), item);
}
+
index++;
if (query.DtoOptions.EnableImages)
@@ -1817,6 +1875,7 @@ namespace Emby.Server.Implementations.Data
{
DeserializeImages(reader.GetString(index), item);
}
+
index++;
}
@@ -1826,6 +1885,7 @@ namespace Emby.Server.Implementations.Data
{
item.ProductionLocations = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries).ToArray();
}
+
index++;
}
@@ -1835,6 +1895,7 @@ namespace Emby.Server.Implementations.Data
{
item.ExtraIds = SplitToGuids(reader.GetString(index));
}
+
index++;
}
@@ -1842,6 +1903,7 @@ namespace Emby.Server.Implementations.Data
{
item.TotalBitrate = reader.GetInt32(index);
}
+
index++;
if (!reader.IsDBNull(index))
@@ -1851,6 +1913,7 @@ namespace Emby.Server.Implementations.Data
item.ExtraType = extraType;
}
}
+
index++;
if (hasArtistFields)
@@ -1859,12 +1922,14 @@ namespace Emby.Server.Implementations.Data
{
hasArtists.Artists = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
}
+
index++;
if (item is IHasAlbumArtist hasAlbumArtists && !reader.IsDBNull(index))
{
hasAlbumArtists.AlbumArtists = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
}
+
index++;
}
@@ -1872,6 +1937,7 @@ namespace Emby.Server.Implementations.Data
{
item.ExternalId = reader.GetString(index);
}
+
index++;
if (HasField(query, ItemFields.SeriesPresentationUniqueKey))
@@ -1883,6 +1949,7 @@ namespace Emby.Server.Implementations.Data
hasSeries.SeriesPresentationUniqueKey = reader.GetString(index);
}
}
+
index++;
}
@@ -1892,6 +1959,7 @@ namespace Emby.Server.Implementations.Data
{
program.ShowId = reader.GetString(index);
}
+
index++;
}
@@ -1899,6 +1967,7 @@ namespace Emby.Server.Implementations.Data
{
item.OwnerId = reader.GetGuid(index);
}
+
index++;
return item;
@@ -1919,7 +1988,7 @@ namespace Emby.Server.Implementations.Data
}
/// <summary>
- /// Gets chapters for an item
+ /// Gets chapters for an item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>IEnumerable{ChapterInfo}.</returns>
@@ -1947,7 +2016,7 @@ namespace Emby.Server.Implementations.Data
}
/// <summary>
- /// Gets a single chapter for an item
+ /// Gets a single chapter for an item.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="index">The index.</param>
@@ -2044,7 +2113,6 @@ namespace Emby.Server.Implementations.Data
db.Execute("delete from " + ChaptersTableName + " where ItemId=@ItemId", idBlob);
InsertChapters(idBlob, chapters, db);
-
}, TransactionMode);
}
}
@@ -2475,6 +2543,7 @@ namespace Emby.Server.Implementations.Data
{
statement.TryBind("@SearchTermStartsWith", searchTerm + "%");
}
+
if (commandText.IndexOf("@SearchTermContains", StringComparison.OrdinalIgnoreCase) != -1)
{
statement.TryBind("@SearchTermContains", "%" + searchTerm + "%");
@@ -2706,22 +2775,85 @@ namespace Emby.Server.Implementations.Data
private string FixUnicodeChars(string buffer)
{
- if (buffer.IndexOf('\u2013') > -1) buffer = buffer.Replace('\u2013', '-'); // en dash
- if (buffer.IndexOf('\u2014') > -1) buffer = buffer.Replace('\u2014', '-'); // em dash
- if (buffer.IndexOf('\u2015') > -1) buffer = buffer.Replace('\u2015', '-'); // horizontal bar
- if (buffer.IndexOf('\u2017') > -1) buffer = buffer.Replace('\u2017', '_'); // double low line
- if (buffer.IndexOf('\u2018') > -1) buffer = buffer.Replace('\u2018', '\''); // left single quotation mark
- if (buffer.IndexOf('\u2019') > -1) buffer = buffer.Replace('\u2019', '\''); // right single quotation mark
- if (buffer.IndexOf('\u201a') > -1) buffer = buffer.Replace('\u201a', ','); // single low-9 quotation mark
- if (buffer.IndexOf('\u201b') > -1) buffer = buffer.Replace('\u201b', '\''); // single high-reversed-9 quotation mark
- if (buffer.IndexOf('\u201c') > -1) buffer = buffer.Replace('\u201c', '\"'); // left double quotation mark
- if (buffer.IndexOf('\u201d') > -1) buffer = buffer.Replace('\u201d', '\"'); // right double quotation mark
- if (buffer.IndexOf('\u201e') > -1) buffer = buffer.Replace('\u201e', '\"'); // double low-9 quotation mark
- if (buffer.IndexOf('\u2026') > -1) buffer = buffer.Replace("\u2026", "..."); // horizontal ellipsis
- if (buffer.IndexOf('\u2032') > -1) buffer = buffer.Replace('\u2032', '\''); // prime
- if (buffer.IndexOf('\u2033') > -1) buffer = buffer.Replace('\u2033', '\"'); // double prime
- if (buffer.IndexOf('\u0060') > -1) buffer = buffer.Replace('\u0060', '\''); // grave accent
- if (buffer.IndexOf('\u00B4') > -1) buffer = buffer.Replace('\u00B4', '\''); // acute accent
+ if (buffer.IndexOf('\u2013') > -1)
+ {
+ buffer = buffer.Replace('\u2013', '-'); // en dash
+ }
+
+ if (buffer.IndexOf('\u2014') > -1)
+ {
+ buffer = buffer.Replace('\u2014', '-'); // em dash
+ }
+
+ if (buffer.IndexOf('\u2015') > -1)
+ {
+ buffer = buffer.Replace('\u2015', '-'); // horizontal bar
+ }
+
+ if (buffer.IndexOf('\u2017') > -1)
+ {
+ buffer = buffer.Replace('\u2017', '_'); // double low line
+ }
+
+ if (buffer.IndexOf('\u2018') > -1)
+ {
+ buffer = buffer.Replace('\u2018', '\''); // left single quotation mark
+ }
+
+ if (buffer.IndexOf('\u2019') > -1)
+ {
+ buffer = buffer.Replace('\u2019', '\''); // right single quotation mark
+ }
+
+ if (buffer.IndexOf('\u201a') > -1)
+ {
+ buffer = buffer.Replace('\u201a', ','); // single low-9 quotation mark
+ }
+
+ if (buffer.IndexOf('\u201b') > -1)
+ {
+ buffer = buffer.Replace('\u201b', '\''); // single high-reversed-9 quotation mark
+ }
+
+ if (buffer.IndexOf('\u201c') > -1)
+ {
+ buffer = buffer.Replace('\u201c', '\"'); // left double quotation mark
+ }
+
+ if (buffer.IndexOf('\u201d') > -1)
+ {
+ buffer = buffer.Replace('\u201d', '\"'); // right double quotation mark
+ }
+
+ if (buffer.IndexOf('\u201e') > -1)
+ {
+ buffer = buffer.Replace('\u201e', '\"'); // double low-9 quotation mark
+ }
+
+ if (buffer.IndexOf('\u2026') > -1)
+ {
+ buffer = buffer.Replace("\u2026", "..."); // horizontal ellipsis
+ }
+
+ if (buffer.IndexOf('\u2032') > -1)
+ {
+ buffer = buffer.Replace('\u2032', '\''); // prime
+ }
+
+ if (buffer.IndexOf('\u2033') > -1)
+ {
+ buffer = buffer.Replace('\u2033', '\"'); // double prime
+ }
+
+ if (buffer.IndexOf('\u0060') > -1)
+ {
+ buffer = buffer.Replace('\u0060', '\''); // grave accent
+ }
+
+ if (buffer.IndexOf('\u00B4') > -1)
+ {
+ buffer = buffer.Replace('\u00B4', '\''); // acute accent
+ }
return buffer;
}
@@ -2745,6 +2877,7 @@ namespace Emby.Server.Implementations.Data
{
items[i] = newItem;
}
+
return;
}
}
@@ -2837,6 +2970,7 @@ namespace Emby.Server.Implementations.Data
{
statementTexts.Add(commandText);
}
+
if (query.EnableTotalRecordCount)
{
commandText = string.Empty;
@@ -3241,6 +3375,7 @@ namespace Emby.Server.Implementations.Data
{
statementTexts.Add(commandText);
}
+
if (query.EnableTotalRecordCount)
{
commandText = string.Empty;
@@ -3594,11 +3729,13 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add("IndexNumber=@IndexNumber");
statement?.TryBind("@IndexNumber", query.IndexNumber.Value);
}
+
if (query.ParentIndexNumber.HasValue)
{
whereClauses.Add("ParentIndexNumber=@ParentIndexNumber");
statement?.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value);
}
+
if (query.ParentIndexNumberNotEquals.HasValue)
{
whereClauses.Add("(ParentIndexNumber<>@ParentIndexNumberNotEquals or ParentIndexNumber is null)");
@@ -3884,6 +4021,7 @@ namespace Emby.Server.Implementations.Data
{
statement.TryBind(paramName, artistId.ToByteArray());
}
+
index++;
}
@@ -3904,6 +4042,7 @@ namespace Emby.Server.Implementations.Data
{
statement.TryBind(paramName, artistId.ToByteArray());
}
+
index++;
}
@@ -3924,8 +4063,10 @@ namespace Emby.Server.Implementations.Data
{
statement.TryBind(paramName, artistId.ToByteArray());
}
+
index++;
}
+
var clause = "(" + string.Join(" OR ", clauses) + ")";
whereClauses.Add(clause);
}
@@ -3943,8 +4084,10 @@ namespace Emby.Server.Implementations.Data
{
statement.TryBind(paramName, albumId.ToByteArray());
}
+
index++;
}
+
var clause = "(" + string.Join(" OR ", clauses) + ")";
whereClauses.Add(clause);
}
@@ -3962,8 +4105,10 @@ namespace Emby.Server.Implementations.Data
{
statement.TryBind(paramName, artistId.ToByteArray());
}
+
index++;
}
+
var clause = "(" + string.Join(" OR ", clauses) + ")";
whereClauses.Add(clause);
}
@@ -3981,8 +4126,10 @@ namespace Emby.Server.Implementations.Data
{
statement.TryBind(paramName, genreId.ToByteArray());
}
+
index++;
}
+
var clause = "(" + string.Join(" OR ", clauses) + ")";
whereClauses.Add(clause);
}
@@ -3998,8 +4145,10 @@ namespace Emby.Server.Implementations.Data
{
statement.TryBind("@Genre" + index, GetCleanValue(item));
}
+
index++;
}
+
var clause = "(" + string.Join(" OR ", clauses) + ")";
whereClauses.Add(clause);
}
@@ -4015,8 +4164,10 @@ namespace Emby.Server.Implementations.Data
{
statement.TryBind("@Tag" + index, GetCleanValue(item));
}
+
index++;
}
+
var clause = "(" + string.Join(" OR ", clauses) + ")";
whereClauses.Add(clause);
}
@@ -4032,8 +4183,10 @@ namespace Emby.Server.Implementations.Data
{
statement.TryBind("@ExcludeTag" + index, GetCleanValue(item));
}
+
index++;
}
+
var clause = "(" + string.Join(" OR ", clauses) + ")";
whereClauses.Add(clause);
}
@@ -4052,8 +4205,10 @@ namespace Emby.Server.Implementations.Data
{
statement.TryBind(paramName, studioId.ToByteArray());
}
+
index++;
}
+
var clause = "(" + string.Join(" OR ", clauses) + ")";
whereClauses.Add(clause);
}
@@ -4069,8 +4224,10 @@ namespace Emby.Server.Implementations.Data
{
statement.TryBind("@OfficialRating" + index, item);
}
+
index++;
}
+
var clause = "(" + string.Join(" OR ", clauses) + ")";
whereClauses.Add(clause);
}
@@ -4245,6 +4402,7 @@ namespace Emby.Server.Implementations.Data
statement.TryBind("@IsVirtualItem", isVirtualItem.Value);
}
}
+
if (query.IsSpecialSeason.HasValue)
{
if (query.IsSpecialSeason.Value)
@@ -4256,6 +4414,7 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add("IndexNumber <> 0");
}
}
+
if (query.IsUnaired.HasValue)
{
if (query.IsUnaired.Value)
@@ -4267,6 +4426,7 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add("PremiereDate < DATETIME('now')");
}
}
+
var queryMediaTypes = query.MediaTypes.Where(IsValidMediaType).ToArray();
if (queryMediaTypes.Length == 1)
{
@@ -4282,6 +4442,7 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add("MediaType in (" + val + ")");
}
+
if (query.ItemIds.Length > 0)
{
var includeIds = new List<string>();
@@ -4294,11 +4455,13 @@ namespace Emby.Server.Implementations.Data
{
statement.TryBind("@IncludeId" + index, id);
}
+
index++;
}
whereClauses.Add("(" + string.Join(" OR ", includeIds) + ")");
}
+
if (query.ExcludeItemIds.Length > 0)
{
var excludeIds = new List<string>();
@@ -4311,6 +4474,7 @@ namespace Emby.Server.Implementations.Data
{
statement.TryBind("@ExcludeId" + index, id);
}
+
index++;
}
@@ -4335,6 +4499,7 @@ namespace Emby.Server.Implementations.Data
{
statement.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
}
+
index++;
break;
@@ -4360,7 +4525,7 @@ namespace Emby.Server.Implementations.Data
// TODO this seems to be an idea for a better schema where ProviderIds are their own table
// buut this is not implemented
- //hasProviderIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = '" + pair.Key + "'), '') <> " + paramName + ")");
+ // hasProviderIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = '" + pair.Key + "'), '') <> " + paramName + ")");
// TODO this is a really BAD way to do it since the pair:
// Tmdb, 1234 matches Tmdb=1234 but also Tmdb=1234567
@@ -4377,6 +4542,7 @@ namespace Emby.Server.Implementations.Data
{
statement.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
}
+
index++;
break;
@@ -4427,6 +4593,7 @@ namespace Emby.Server.Implementations.Data
{
whereClauses.Add("(TopParentId=@TopParentId)");
}
+
if (statement != null)
{
statement.TryBind("@TopParentId", queryTopParentIds[0].ToString("N", CultureInfo.InvariantCulture));
@@ -4464,11 +4631,13 @@ namespace Emby.Server.Implementations.Data
statement.TryBind("@AncestorId", query.AncestorIds[0]);
}
}
+
if (query.AncestorIds.Length > 1)
{
var inClause = string.Join(",", query.AncestorIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
whereClauses.Add(string.Format("Guid in (select itemId from AncestorIds where AncestorIdText in ({0}))", inClause));
}
+
if (!string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey))
{
var inClause = "select guid from TypedBaseItems where PresentationUniqueKey=@AncestorWithPresentationUniqueKey";
@@ -4497,6 +4666,7 @@ namespace Emby.Server.Implementations.Data
statement.TryBind("@UnratedType", query.BlockUnratedItems[0].ToString());
}
}
+
if (query.BlockUnratedItems.Length > 1)
{
var inClause = string.Join(",", query.BlockUnratedItems.Select(i => "'" + i.ToString() + "'"));
@@ -4789,7 +4959,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
connection.RunInTransaction(db =>
{
connection.ExecuteAll(sql);
-
}, TransactionMode);
}
}
@@ -4972,6 +5141,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
statement.TryBind("@ItemId", query.ItemId.ToByteArray());
}
}
+
if (!query.AppearsInItemId.Equals(Guid.Empty))
{
whereClauses.Add("Name in (Select Name from People where ItemId=@AppearsInItemId)");
@@ -4980,6 +5150,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToByteArray());
}
}
+
var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList();
if (queryPersonTypes.Count == 1)
@@ -4996,6 +5167,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
whereClauses.Add("PersonType in (" + val + ")");
}
+
var queryExcludePersonTypes = query.ExcludePersonTypes.Where(IsValidPersonType).ToList();
if (queryExcludePersonTypes.Count == 1)
@@ -5012,6 +5184,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
whereClauses.Add("PersonType not in (" + val + ")");
}
+
if (query.MaxListOrder.HasValue)
{
whereClauses.Add("ListOrder<=@MaxListOrder");
@@ -5020,6 +5193,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
statement.TryBind("@MaxListOrder", query.MaxListOrder.Value);
}
}
+
if (!string.IsNullOrWhiteSpace(query.NameContains))
{
whereClauses.Add("Name like @NameContains");
@@ -5159,6 +5333,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
var typeString = string.Join(",", withItemTypes.Select(i => "'" + i + "'"));
commandText += " AND ItemId In (select guid from typedbaseitems where type in (" + typeString + "))";
}
+
if (excludeItemTypes.Count > 0)
{
var typeString = string.Join(",", excludeItemTypes.Select(i => "'" + i + "'"));
@@ -5180,7 +5355,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
}
}
}
-
}
LogQueryTime("GetItemValueNames", commandText, now);
@@ -5631,7 +5805,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
db.Execute("delete from People where ItemId=@ItemId", itemIdBlob);
InsertPeople(itemIdBlob, people, db);
-
}, TransactionMode);
}
}
@@ -5788,7 +5961,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob);
InsertMediaStreams(itemIdBlob, streams, db);
-
}, TransactionMode);
}
}
@@ -6134,7 +6306,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
db.Execute("delete from mediaattachments where ItemId=@ItemId", itemIdBlob);
InsertMediaAttachments(itemIdBlob, attachments, db, cancellationToken);
-
}, TransactionMode);
}
}
@@ -6200,7 +6371,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
/// Gets the attachment.
/// </summary>
/// <param name="reader">The reader.</param>
- /// <returns>MediaAttachment</returns>
+ /// <returns>MediaAttachment.</returns>
private MediaAttachment GetMediaAttachment(IReadOnlyList<IResultSetValue> reader)
{
var item = new MediaAttachment
diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
index b99b74ef8..4a78aac8e 100644
--- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
@@ -135,10 +135,12 @@ namespace Emby.Server.Implementations.Data
{
throw new ArgumentNullException(nameof(userData));
}
+
if (internalUserId <= 0)
{
throw new ArgumentNullException(nameof(internalUserId));
}
+
if (string.IsNullOrEmpty(key))
{
throw new ArgumentNullException(nameof(key));
@@ -153,6 +155,7 @@ namespace Emby.Server.Implementations.Data
{
throw new ArgumentNullException(nameof(userData));
}
+
if (internalUserId <= 0)
{
throw new ArgumentNullException(nameof(internalUserId));
@@ -235,7 +238,7 @@ namespace Emby.Server.Implementations.Data
}
/// <summary>
- /// Persist all user data for the specified user
+ /// Persist all user data for the specified user.
/// </summary>
private void PersistAllUserData(long internalUserId, UserItemData[] userDataList, CancellationToken cancellationToken)
{
@@ -309,7 +312,7 @@ namespace Emby.Server.Implementations.Data
}
/// <summary>
- /// Return all user-data associated with the given user
+ /// Return all user-data associated with the given user.
/// </summary>
/// <param name="internalUserId"></param>
/// <returns></returns>
@@ -339,7 +342,7 @@ namespace Emby.Server.Implementations.Data
}
/// <summary>
- /// Read a row from the specified reader into the provided userData object
+ /// Read a row from the specified reader into the provided userData object.
/// </summary>
/// <param name="reader"></param>
private UserItemData ReadRow(IReadOnlyList<IResultSetValue> reader)
@@ -347,7 +350,7 @@ namespace Emby.Server.Implementations.Data
var userData = new UserItemData();
userData.Key = reader[0].ToString();
- //userData.UserId = reader[1].ReadGuidFromBlob();
+ // userData.UserId = reader[1].ReadGuidFromBlob();
if (reader[2].SQLiteType != SQLiteType.Null)
{
diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs
index f8896d69f..e75745cc6 100644
--- a/Emby.Server.Implementations/Devices/DeviceManager.cs
+++ b/Emby.Server.Implementations/Devices/DeviceManager.cs
@@ -112,7 +112,7 @@ namespace Emby.Server.Implementations.Devices
{
IEnumerable<AuthenticationInfo> sessions = _authRepo.Get(new AuthenticationInfoQuery
{
- //UserId = query.UserId
+ // UserId = query.UserId
HasUser = true
}).Items;
@@ -169,6 +169,7 @@ namespace Emby.Server.Implementations.Devices
{
throw new ArgumentException("user not found");
}
+
if (string.IsNullOrEmpty(deviceId))
{
throw new ArgumentNullException(nameof(deviceId));
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index 7e0cfe424..c967e9230 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -74,7 +74,7 @@ namespace Emby.Server.Implementations.Dto
}
/// <summary>
- /// Converts a BaseItem to a DTOBaseItem
+ /// Converts a BaseItem to a DTOBaseItem.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="fields">The fields.</param>
@@ -277,6 +277,7 @@ namespace Emby.Server.Implementations.Dto
dto.EpisodeTitle = dto.Name;
dto.Name = dto.SeriesName;
}
+
liveTvManager.AddInfoToRecordingDto(item, dto, activeRecording, user);
}
@@ -292,6 +293,7 @@ namespace Emby.Server.Implementations.Dto
{
continue;
}
+
var containers = container.Split(new[] { ',' });
if (containers.Length < 2)
{
@@ -406,7 +408,6 @@ namespace Emby.Server.Implementations.Dto
dto.DateLastMediaAdded = folder.DateLastMediaAdded;
}
}
-
else
{
if (options.EnableUserData)
@@ -443,7 +444,7 @@ namespace Emby.Server.Implementations.Dto
}
/// <summary>
- /// Gets client-side Id of a server-side BaseItem
+ /// Gets client-side Id of a server-side BaseItem.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>System.String.</returns>
@@ -457,6 +458,7 @@ namespace Emby.Server.Implementations.Dto
{
dto.SeriesName = item.SeriesName;
}
+
private static void SetPhotoProperties(BaseItemDto dto, Photo item)
{
dto.CameraMake = item.CameraMake;
@@ -538,7 +540,7 @@ namespace Emby.Server.Implementations.Dto
}
/// <summary>
- /// Attaches People DTO's to a DTOBaseItem
+ /// Attaches People DTO's to a DTOBaseItem.
/// </summary>
/// <param name="dto">The dto.</param>
/// <param name="item">The item.</param>
@@ -555,22 +557,27 @@ namespace Emby.Server.Implementations.Dto
{
return 0;
}
+
if (i.IsType(PersonType.GuestStar))
{
return 1;
}
+
if (i.IsType(PersonType.Director))
{
return 2;
}
+
if (i.IsType(PersonType.Writer))
{
return 3;
}
+
if (i.IsType(PersonType.Producer))
{
return 4;
}
+
if (i.IsType(PersonType.Composer))
{
return 4;
@@ -594,7 +601,6 @@ namespace Emby.Server.Implementations.Dto
_logger.LogError(ex, "Error getting person {Name}", c);
return null;
}
-
}).Where(i => i != null)
.GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
.Select(x => x.First())
@@ -615,6 +621,7 @@ namespace Emby.Server.Implementations.Dto
{
baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary);
baseItemPerson.Id = entity.Id.ToString("N", CultureInfo.InvariantCulture);
+ baseItemPerson.ImageBlurHashes = dto.ImageBlurHashes;
list.Add(baseItemPerson);
}
}
@@ -727,7 +734,7 @@ namespace Emby.Server.Implementations.Dto
}
/// <summary>
- /// Sets simple property values on a DTOBaseItem
+ /// Sets simple property values on a DTOBaseItem.
/// </summary>
/// <param name="dto">The dto.</param>
/// <param name="item">The item.</param>
@@ -947,7 +954,7 @@ namespace Emby.Server.Implementations.Dto
dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary);
}
- //if (options.ContainsField(ItemFields.MediaSourceCount))
+ // if (options.ContainsField(ItemFields.MediaSourceCount))
//{
// Songs always have one
//}
@@ -957,13 +964,13 @@ namespace Emby.Server.Implementations.Dto
{
dto.Artists = hasArtist.Artists;
- //var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
+ // var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
//{
// EnableTotalRecordCount = false,
// ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
//});
- //dto.ArtistItems = artistItems.Items
+ // dto.ArtistItems = artistItems.Items
// .Select(i =>
// {
// var artist = i.Item1;
@@ -976,7 +983,7 @@ namespace Emby.Server.Implementations.Dto
// .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 foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
dto.ArtistItems = hasArtist.Artists
//.Except(foundArtists, new DistinctNameComparer())
.Select(i =>
@@ -1001,7 +1008,6 @@ namespace Emby.Server.Implementations.Dto
}
return null;
-
}).Where(i => i != null).ToArray();
}
@@ -1010,13 +1016,13 @@ namespace Emby.Server.Implementations.Dto
{
dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
- //var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
+ // var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
//{
// EnableTotalRecordCount = false,
// ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
//});
- //dto.AlbumArtists = artistItems.Items
+ // dto.AlbumArtists = artistItems.Items
// .Select(i =>
// {
// var artist = i.Item1;
@@ -1052,7 +1058,6 @@ namespace Emby.Server.Implementations.Dto
}
return null;
-
}).Where(i => i != null).ToArray();
}
@@ -1166,7 +1171,7 @@ namespace Emby.Server.Implementations.Dto
// this block will add the series poster for episodes without a poster
// TODO maybe remove the if statement entirely
- //if (options.ContainsField(ItemFields.SeriesPrimaryImage))
+ // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
{
episodeSeries = episodeSeries ?? episode.Series;
if (episodeSeries != null)
@@ -1212,7 +1217,7 @@ namespace Emby.Server.Implementations.Dto
// this block will add the series poster for seasons without a poster
// TODO maybe remove the if statement entirely
- //if (options.ContainsField(ItemFields.SeriesPrimaryImage))
+ // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
{
series = series ?? season.Series;
if (series != null)
@@ -1350,6 +1355,7 @@ namespace Emby.Server.Implementations.Dto
dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image);
}
}
+
if (artLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtItemId == null)
{
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art);
@@ -1360,6 +1366,7 @@ namespace Emby.Server.Implementations.Dto
dto.ParentArtImageTag = GetTagAndFillBlurhash(dto, parent, image);
}
}
+
if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId == null || parent is Series) && !(parent is ICollectionFolder) && !(parent is UserView))
{
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
@@ -1370,6 +1377,7 @@ namespace Emby.Server.Implementations.Dto
dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image);
}
}
+
if (backdropLimit > 0 && !((dto.BackdropImageTags != null && dto.BackdropImageTags.Length > 0) || (dto.ParentBackdropImageTags != null && dto.ParentBackdropImageTags.Length > 0)))
{
var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList();
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 5272e2692..adae920dc 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -23,8 +23,8 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="IPNetwork2" Version="2.4.0.126" />
- <PackageReference Include="Jellyfin.XmlTv" Version="10.4.3" />
+ <PackageReference Include="IPNetwork2" Version="2.5.211" />
+ <PackageReference Include="Jellyfin.XmlTv" Version="10.6.0-pre1" />
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" Version="2.2.0" />
@@ -33,14 +33,14 @@
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
- <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.5" />
- <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.5" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.5" />
- <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.5" />
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.6" />
+ <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.6" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.6" />
+ <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.6" />
<PackageReference Include="Mono.Nat" Version="2.0.1" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.3.1" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.9.0" />
- <PackageReference Include="sharpcompress" Version="0.25.0" />
+ <PackageReference Include="sharpcompress" Version="0.25.1" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
<PackageReference Include="DotNet.Glob" Version="3.0.9" />
</ItemGroup>
diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
index 02ae55071..c1068522a 100644
--- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
@@ -132,7 +132,6 @@ namespace Emby.Server.Implementations.EntryPoints
}
catch
{
-
}
}
}
diff --git a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs
index 083fe4237..826d4d8dc 100644
--- a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs
@@ -159,7 +159,6 @@ namespace Emby.Server.Implementations.EntryPoints
}
catch (Exception)
{
-
}
}
@@ -175,7 +174,6 @@ namespace Emby.Server.Implementations.EntryPoints
}
catch (Exception)
{
-
}
}
diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
index ecdce89ce..9486874d5 100644
--- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
+++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
@@ -1,3 +1,4 @@
+using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.Udp;
@@ -43,14 +44,21 @@ namespace Emby.Server.Implementations.EntryPoints
_logger = logger;
_appHost = appHost;
_config = configuration;
-
}
/// <inheritdoc />
public Task RunAsync()
{
- _udpServer = new UdpServer(_logger, _appHost, _config);
- _udpServer.Start(PortNumber, _cancellationTokenSource.Token);
+ try
+ {
+ _udpServer = new UdpServer(_logger, _appHost, _config);
+ _udpServer.Start(PortNumber, _cancellationTokenSource.Token);
+ }
+ catch (SocketException ex)
+ {
+ _logger.LogWarning(ex, "Unable to start AutoDiscovery listener on UDP port {PortNumber}", PortNumber);
+ }
+
return Task.CompletedTask;
}
diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
index 87977494a..25adc5812 100644
--- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
+++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
@@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.HttpClientManager
=> SendAsync(options, HttpMethod.Get);
/// <summary>
- /// Performs a GET request and returns the resulting stream
+ /// Performs a GET request and returns the resulting stream.
/// </summary>
/// <param name="options">The options.</param>
/// <returns>Task{Stream}.</returns>
diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs
index 0b61e40b0..590eee1b4 100644
--- a/Emby.Server.Implementations/HttpServer/FileWriter.cs
+++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs
@@ -32,12 +32,12 @@ namespace Emby.Server.Implementations.HttpServer
private readonly IFileSystem _fileSystem;
/// <summary>
- /// The _options
+ /// The _options.
/// </summary>
private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
/// <summary>
- /// The _requested ranges
+ /// The _requested ranges.
/// </summary>
private List<KeyValuePair<long, long?>> _requestedRanges;
diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
index 1b6e4b554..c3428ee62 100644
--- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -453,6 +453,7 @@ namespace Emby.Server.Implementations.HttpServer
{
httpRes.Headers.Add(key, value);
}
+
httpRes.ContentType = "text/plain";
await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false);
return;
@@ -591,7 +592,7 @@ namespace Emby.Server.Implementations.HttpServer
}
/// <summary>
- /// Get the default CORS headers
+ /// Get the default CORS headers.
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
index cc4797790..970f5119c 100644
--- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
@@ -426,7 +426,7 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, StaticResultOptions options)
{
- bool noCache = (requestContext.Headers[HeaderNames.CacheControl].ToString()).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1;
+ bool noCache = requestContext.Headers[HeaderNames.CacheControl].ToString().IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1;
AddCachingHeaders(responseHeaders, options.CacheDuration, noCache, options.DateLastModified);
if (!noCache)
@@ -580,13 +580,12 @@ namespace Emby.Server.Implementations.HttpServer
}
catch (NotSupportedException)
{
-
}
}
if (!string.IsNullOrWhiteSpace(rangeHeader) && totalContentLength.HasValue)
{
- var hasHeaders = new RangeRequestWriter(rangeHeader, totalContentLength.Value, stream, contentType, isHeadRequest, _logger)
+ var hasHeaders = new RangeRequestWriter(rangeHeader, totalContentLength.Value, stream, contentType, isHeadRequest)
{
OnComplete = options.OnComplete
};
@@ -623,8 +622,11 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary>
/// Adds the caching responseHeaders.
/// </summary>
- private void AddCachingHeaders(IDictionary<string, string> responseHeaders, TimeSpan? cacheDuration,
- bool noCache, DateTime? lastModifiedDate)
+ private void AddCachingHeaders(
+ IDictionary<string, string> responseHeaders,
+ TimeSpan? cacheDuration,
+ bool noCache,
+ DateTime? lastModifiedDate)
{
if (noCache)
{
@@ -693,7 +695,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary>
- /// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that
+ /// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that.
/// </summary>
/// <param name="date">The date.</param>
/// <returns>DateTime.</returns>
diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs
index 8b9028f6b..980c2cd3a 100644
--- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs
+++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs
@@ -1,6 +1,7 @@
#pragma warning disable CS1591
using System;
+using System.Buffers;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
@@ -8,46 +9,17 @@ using System.Net;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.HttpServer
{
public class RangeRequestWriter : IAsyncStreamWriter, IHttpResult
{
- /// <summary>
- /// Gets or sets the source stream.
- /// </summary>
- /// <value>The source stream.</value>
- private Stream SourceStream { get; set; }
- private string RangeHeader { get; set; }
- private bool IsHeadRequest { get; set; }
-
- private long RangeStart { get; set; }
- private long RangeEnd { get; set; }
- private long RangeLength { get; set; }
- private long TotalContentLength { get; set; }
-
- public Action OnComplete { get; set; }
- private readonly ILogger _logger;
-
private const int BufferSize = 81920;
- /// <summary>
- /// The _options
- /// </summary>
private readonly Dictionary<string, string> _options = new Dictionary<string, string>();
- /// <summary>
- /// The us culture
- /// </summary>
- private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- /// <summary>
- /// Additional HTTP Headers
- /// </summary>
- /// <value>The headers.</value>
- public IDictionary<string, string> Headers => _options;
+ private List<KeyValuePair<long, long?>> _requestedRanges;
/// <summary>
/// Initializes a new instance of the <see cref="RangeRequestWriter" /> class.
@@ -57,8 +29,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="source">The source.</param>
/// <param name="contentType">Type of the content.</param>
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
- /// <param name="logger">The logger instance.</param>
- public RangeRequestWriter(string rangeHeader, long contentLength, Stream source, string contentType, bool isHeadRequest, ILogger logger)
+ public RangeRequestWriter(string rangeHeader, long contentLength, Stream source, string contentType, bool isHeadRequest)
{
if (string.IsNullOrEmpty(contentType))
{
@@ -68,7 +39,6 @@ namespace Emby.Server.Implementations.HttpServer
RangeHeader = rangeHeader;
SourceStream = source;
IsHeadRequest = isHeadRequest;
- this._logger = logger;
ContentType = contentType;
Headers[HeaderNames.ContentType] = contentType;
@@ -79,40 +49,26 @@ namespace Emby.Server.Implementations.HttpServer
}
/// <summary>
- /// Sets the range values.
+ /// Gets or sets the source stream.
/// </summary>
- private void SetRangeValues(long contentLength)
- {
- var requestedRange = RequestedRanges[0];
-
- TotalContentLength = contentLength;
-
- // If the requested range is "0-", we can optimize by just doing a stream copy
- if (!requestedRange.Value.HasValue)
- {
- RangeEnd = TotalContentLength - 1;
- }
- else
- {
- RangeEnd = requestedRange.Value.Value;
- }
-
- RangeStart = requestedRange.Key;
- RangeLength = 1 + RangeEnd - RangeStart;
+ /// <value>The source stream.</value>
+ private Stream SourceStream { get; set; }
+ private string RangeHeader { get; set; }
+ private bool IsHeadRequest { get; set; }
- Headers[HeaderNames.ContentLength] = RangeLength.ToString(CultureInfo.InvariantCulture);
- Headers[HeaderNames.ContentRange] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}";
+ private long RangeStart { get; set; }
+ private long RangeEnd { get; set; }
+ private long RangeLength { get; set; }
+ private long TotalContentLength { get; set; }
- if (RangeStart > 0 && SourceStream.CanSeek)
- {
- SourceStream.Position = RangeStart;
- }
- }
+ public Action OnComplete { get; set; }
/// <summary>
- /// The _requested ranges
+ /// Additional HTTP Headers
/// </summary>
- private List<KeyValuePair<long, long?>> _requestedRanges;
+ /// <value>The headers.</value>
+ public IDictionary<string, string> Headers => _options;
+
/// <summary>
/// Gets the requested ranges.
/// </summary>
@@ -137,11 +93,12 @@ namespace Emby.Server.Implementations.HttpServer
if (!string.IsNullOrEmpty(vals[0]))
{
- start = long.Parse(vals[0], UsCulture);
+ start = long.Parse(vals[0], CultureInfo.InvariantCulture);
}
+
if (!string.IsNullOrEmpty(vals[1]))
{
- end = long.Parse(vals[1], UsCulture);
+ end = long.Parse(vals[1], CultureInfo.InvariantCulture);
}
_requestedRanges.Add(new KeyValuePair<long, long?>(start, end));
@@ -152,6 +109,51 @@ namespace Emby.Server.Implementations.HttpServer
}
}
+ public string ContentType { get; set; }
+
+ public IRequest RequestContext { get; set; }
+
+ public object Response { get; set; }
+
+ public int Status { get; set; }
+
+ public HttpStatusCode StatusCode
+ {
+ get => (HttpStatusCode)Status;
+ set => Status = (int)value;
+ }
+
+ /// <summary>
+ /// Sets the range values.
+ /// </summary>
+ private void SetRangeValues(long contentLength)
+ {
+ var requestedRange = RequestedRanges[0];
+
+ TotalContentLength = contentLength;
+
+ // If the requested range is "0-", we can optimize by just doing a stream copy
+ if (!requestedRange.Value.HasValue)
+ {
+ RangeEnd = TotalContentLength - 1;
+ }
+ else
+ {
+ RangeEnd = requestedRange.Value.Value;
+ }
+
+ RangeStart = requestedRange.Key;
+ RangeLength = 1 + RangeEnd - RangeStart;
+
+ Headers[HeaderNames.ContentLength] = RangeLength.ToString(CultureInfo.InvariantCulture);
+ Headers[HeaderNames.ContentRange] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}";
+
+ if (RangeStart > 0 && SourceStream.CanSeek)
+ {
+ SourceStream.Position = RangeStart;
+ }
+ }
+
public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken)
{
try
@@ -167,59 +169,44 @@ namespace Emby.Server.Implementations.HttpServer
// If the requested range is "0-", we can optimize by just doing a stream copy
if (RangeEnd >= TotalContentLength - 1)
{
- await source.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false);
+ await source.CopyToAsync(responseStream, BufferSize, cancellationToken).ConfigureAwait(false);
}
else
{
- await CopyToInternalAsync(source, responseStream, RangeLength).ConfigureAwait(false);
+ await CopyToInternalAsync(source, responseStream, RangeLength, cancellationToken).ConfigureAwait(false);
}
}
}
finally
{
- if (OnComplete != null)
- {
- OnComplete();
- }
+ OnComplete?.Invoke();
}
}
- private static async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength)
+ private static async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
{
- var array = new byte[BufferSize];
- int bytesRead;
- while ((bytesRead = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0)
+ var array = ArrayPool<byte>.Shared.Rent(BufferSize);
+ try
{
- if (bytesRead == 0)
+ int bytesRead;
+ while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0)
{
- break;
- }
+ var bytesToCopy = Math.Min(bytesRead, copyLength);
- var bytesToCopy = Math.Min(bytesRead, copyLength);
+ await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy), cancellationToken).ConfigureAwait(false);
- await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy)).ConfigureAwait(false);
+ copyLength -= bytesToCopy;
- copyLength -= bytesToCopy;
-
- if (copyLength <= 0)
- {
- break;
+ if (copyLength <= 0)
+ {
+ break;
+ }
}
}
- }
-
- public string ContentType { get; set; }
-
- public IRequest RequestContext { get; set; }
-
- public object Response { get; set; }
-
- public int Status { get; set; }
-
- public HttpStatusCode StatusCode
- {
- get => (HttpStatusCode)Status;
- set => Status = (int)value;
+ finally
+ {
+ ArrayPool<byte>.Shared.Return(array);
+ }
}
}
}
diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs
index 3ab5dbc16..a8cd2ac8f 100644
--- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs
+++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs
@@ -41,11 +41,11 @@ namespace Emby.Server.Implementations.HttpServer
res.Headers.Add(key, value);
}
// Try to prevent compatibility view
- res.Headers["Access-Control-Allow-Headers"] = ("Accept, Accept-Language, Authorization, Cache-Control, " +
+ res.Headers["Access-Control-Allow-Headers"] = "Accept, Accept-Language, Authorization, Cache-Control, " +
"Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, " +
"Content-Type, Cookie, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, " +
"Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, " +
- "X-Emby-Authorization");
+ "X-Emby-Authorization";
if (dto is Exception exception)
{
diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs
index 49bca7dac..a32b03aaa 100644
--- a/Emby.Server.Implementations/IO/LibraryMonitor.cs
+++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs
@@ -266,7 +266,6 @@ namespace Emby.Server.Implementations.IO
{
DisposeWatcher(newWatcher, false);
}
-
}
catch (Exception ex)
{
@@ -393,7 +392,6 @@ namespace Emby.Server.Implementations.IO
}
return false;
-
}))
{
monitorPath = false;
diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
index 2bcfc82b6..ab6483bf9 100644
--- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
@@ -237,7 +237,7 @@ namespace Emby.Server.Implementations.IO
{
result.IsDirectory = info is DirectoryInfo || (info.Attributes & FileAttributes.Directory) == FileAttributes.Directory;
- //if (!result.IsDirectory)
+ // if (!result.IsDirectory)
//{
// result.IsHidden = (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
//}
@@ -245,6 +245,16 @@ namespace Emby.Server.Implementations.IO
if (info is FileInfo fileInfo)
{
result.Length = fileInfo.Length;
+
+ // Issue #2354 get the size of files behind symbolic links
+ if (fileInfo.Attributes.HasFlag(FileAttributes.ReparsePoint))
+ {
+ using (Stream thisFileStream = File.OpenRead(fileInfo.FullName))
+ {
+ result.Length = thisFileStream.Length;
+ }
+ }
+
result.DirectoryName = fileInfo.DirectoryName;
}
@@ -628,6 +638,7 @@ namespace Emby.Server.Implementations.IO
{
return false;
}
+
return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
});
}
@@ -682,6 +693,7 @@ namespace Emby.Server.Implementations.IO
{
return false;
}
+
return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
});
}
diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs
index 0b9f80538..e7e72c686 100644
--- a/Emby.Server.Implementations/IStartupOptions.cs
+++ b/Emby.Server.Implementations/IStartupOptions.cs
@@ -37,11 +37,6 @@ namespace Emby.Server.Implementations
string RestartArgs { get; }
/// <summary>
- /// Gets the value of the --plugin-manifest-url command line option.
- /// </summary>
- string PluginManifestUrl { get; }
-
- /// <summary>
/// Gets the value of the --published-server-url command line option.
/// </summary>
Uri PublishedServerUrl { get; }
diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
index dc8062b45..da88b8d8a 100644
--- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
+++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
@@ -71,7 +71,6 @@ namespace Emby.Server.Implementations.Images
new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending)
},
IncludeItemTypes = includeItemTypes
-
});
}
diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs
index ca0aa4a9f..462eb03a8 100644
--- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs
+++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs
@@ -78,7 +78,6 @@ namespace Emby.Server.Implementations.Images
}
return i;
-
}).GroupBy(x => x.Id)
.Select(x => x.First());
diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
index 218e5a0c6..77b2c0a69 100644
--- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
+++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
@@ -8,24 +9,33 @@ using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library
{
/// <summary>
- /// Provides the core resolver ignore rules
+ /// Provides the core resolver ignore rules.
/// </summary>
public class CoreResolutionIgnoreRule : IResolverIgnoreRule
{
private readonly ILibraryManager _libraryManager;
+ private readonly IServerApplicationPaths _serverApplicationPaths;
/// <summary>
/// Initializes a new instance of the <see cref="CoreResolutionIgnoreRule"/> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
- public CoreResolutionIgnoreRule(ILibraryManager libraryManager)
+ /// <param name="serverApplicationPaths">The server application paths.</param>
+ public CoreResolutionIgnoreRule(ILibraryManager libraryManager, IServerApplicationPaths serverApplicationPaths)
{
_libraryManager = libraryManager;
+ _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;
+ }
+
// Don't ignore top level folders
if (fileInfo.IsDirectory && parent is AggregateFolder)
{
diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
index 9a7186898..ab39a7223 100644
--- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
+++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
@@ -12,11 +12,13 @@ namespace Emby.Server.Implementations.Library
public class ExclusiveLiveStream : ILiveStream
{
public int ConsumerCount { get; set; }
+
public string OriginalStreamId { get; set; }
public string TunerHostId => null;
public bool EnableStreamSharing { get; set; }
+
public MediaSourceInfo MediaSource { get; set; }
public string UniqueId { get; private set; }
diff --git a/Emby.Server.Implementations/Library/IgnorePatterns.cs b/Emby.Server.Implementations/Library/IgnorePatterns.cs
index d12b5855b..6e6ef1359 100644
--- a/Emby.Server.Implementations/Library/IgnorePatterns.cs
+++ b/Emby.Server.Implementations/Library/IgnorePatterns.cs
@@ -1,17 +1,20 @@
+#nullable enable
+
+using System;
using System.Linq;
using DotNet.Globbing;
namespace Emby.Server.Implementations.Library
{
/// <summary>
- /// Glob patterns for files to ignore
+ /// Glob patterns for files to ignore.
/// </summary>
public static class IgnorePatterns
{
/// <summary>
- /// Files matching these glob patterns will be ignored
+ /// Files matching these glob patterns will be ignored.
/// </summary>
- public static readonly string[] Patterns = new string[]
+ private static readonly string[] _patterns =
{
"**/small.jpg",
"**/albumart.jpg",
@@ -19,32 +22,51 @@ namespace Emby.Server.Implementations.Library
// 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",
// 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 and directories
"**/.*/**",
+ "**/.*",
// thumbs.db
"**/thumbs.db",
@@ -56,19 +78,31 @@ namespace Emby.Server.Implementations.Library
private static readonly GlobOptions _globOptions = new GlobOptions
{
- Evaluation = {
+ Evaluation =
+ {
CaseInsensitive = true
}
};
- private static readonly Glob[] _globs = Patterns.Select(p => Glob.Parse(p, _globOptions)).ToArray();
+ private static readonly Glob[] _globs = _patterns.Select(p => Glob.Parse(p, _globOptions)).ToArray();
/// <summary>
- /// Returns true if the supplied path should be ignored
+ /// Returns true if the supplied path should be ignored.
/// </summary>
- public static bool ShouldIgnore(string path)
+ /// <param name="path">The path to test.</param>
+ /// <returns>Whether to ignore the path.</returns>
+ public static bool ShouldIgnore(ReadOnlySpan<char> path)
{
- return _globs.Any(g => g.IsMatch(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/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 7d74ced6c..c27b73c74 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -97,13 +97,13 @@ namespace Emby.Server.Implementations.Library
private IIntroProvider[] IntroProviders { get; set; }
/// <summary>
- /// Gets or sets the list of entity resolution ignore rules
+ /// 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
+ /// Gets or sets the list of currently registered entity resolvers.
/// </summary>
/// <value>The entity resolvers enumerable.</value>
private IItemResolver[] EntityResolvers { get; set; }
@@ -136,7 +136,7 @@ namespace Emby.Server.Implementations.Library
/// <summary>
/// Initializes a new instance of the <see cref="LibraryManager" /> class.
/// </summary>
- /// <param name="appHost">The application host</param>
+ /// <param name="appHost">The application host.</param>
/// <param name="logger">The logger.</param>
/// <param name="taskManager">The task manager.</param>
/// <param name="userManager">The user manager.</param>
@@ -209,12 +209,12 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
- /// The _root folder
+ /// The _root folder.
/// </summary>
private volatile AggregateFolder _rootFolder;
/// <summary>
- /// The _root folder sync lock
+ /// The _root folder sync lock.
/// </summary>
private readonly object _rootFolderSyncLock = new object();
@@ -341,7 +341,7 @@ namespace Emby.Server.Implementations.Library
if (item is LiveTvProgram)
{
_logger.LogDebug(
- "Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
+ "Removing item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
item.GetType().Name,
item.Name ?? "Unknown name",
item.Path ?? string.Empty,
@@ -350,7 +350,7 @@ namespace Emby.Server.Implementations.Library
else
{
_logger.LogInformation(
- "Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
+ "Removing item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
item.GetType().Name,
item.Name ?? "Unknown name",
item.Path ?? string.Empty,
@@ -368,7 +368,12 @@ namespace Emby.Server.Implementations.Library
continue;
}
- _logger.LogDebug("Deleting path {MetadataPath}", metadataPath);
+ _logger.LogDebug(
+ "Deleting metadata path, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
+ item.GetType().Name,
+ item.Name ?? "Unknown name",
+ metadataPath,
+ item.Id);
try
{
@@ -392,7 +397,13 @@ namespace Emby.Server.Implementations.Library
{
try
{
- _logger.LogDebug("Deleting path {path}", fileSystemInfo.FullName);
+ _logger.LogInformation(
+ "Deleting item path, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
+ item.GetType().Name,
+ item.Name ?? "Unknown name",
+ fileSystemInfo.FullName,
+ item.Id);
+
if (fileSystemInfo.IsDirectory)
{
Directory.Delete(fileSystemInfo.FullName, true);
@@ -514,8 +525,8 @@ namespace Emby.Server.Implementations.Library
return key.GetMD5();
}
- public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null, bool allowIgnorePath = true)
- => ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent, allowIgnorePath: allowIgnorePath);
+ public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null)
+ => ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent);
private BaseItem ResolvePath(
FileSystemMetadata fileInfo,
@@ -523,8 +534,7 @@ namespace Emby.Server.Implementations.Library
IItemResolver[] resolvers,
Folder parent = null,
string collectionType = null,
- LibraryOptions libraryOptions = null,
- bool allowIgnorePath = true)
+ LibraryOptions libraryOptions = null)
{
if (fileInfo == null)
{
@@ -548,7 +558,7 @@ namespace Emby.Server.Implementations.Library
};
// Return null if ignore rules deem that we should do so
- if (allowIgnorePath && IgnoreFile(args.FileInfo, args.Parent))
+ if (IgnoreFile(args.FileInfo, args.Parent))
{
return null;
}
@@ -627,7 +637,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
- /// Determines whether a path should be ignored based on its contents - called after the contents have been read
+ /// Determines whether a path should be ignored based on its contents - called after the contents have been read.
/// </summary>
/// <param name="args">The args.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
@@ -713,7 +723,7 @@ namespace Emby.Server.Implementations.Library
Directory.CreateDirectory(rootFolderPath);
var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ??
- ((Folder) ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath), allowIgnorePath: false))
+ ((Folder) ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath)))
.DeepCopy<Folder, AggregateFolder>();
// In case program data folder was moved
@@ -795,7 +805,7 @@ namespace Emby.Server.Implementations.Library
if (tmpItem == null)
{
_logger.LogDebug("Creating new userRootFolder with DeepCopy");
- tmpItem = ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(userRootPath), allowIgnorePath: false)).DeepCopy<Folder, UserRootFolder>();
+ tmpItem = ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(userRootPath))).DeepCopy<Folder, UserRootFolder>();
}
// In case program data folder was moved
@@ -909,7 +919,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
- /// Gets a Genre
+ /// Gets a Genre.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>Task{Genre}.</returns>
@@ -990,7 +1000,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
- /// Reloads the root media folder
+ /// Reloads the root media folder.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
@@ -1793,7 +1803,7 @@ namespace Emby.Server.Implementations.Library
/// Creates the items.
/// </summary>
/// <param name="items">The items.</param>
- /// <param name="parent">The parent item</param>
+ /// <param name="parent">The parent item.</param>
/// <param name="cancellationToken">The cancellation token.</param>
public void CreateItems(IEnumerable<BaseItem> items, BaseItem parent, CancellationToken cancellationToken)
{
@@ -1894,9 +1904,19 @@ namespace Emby.Server.Implementations.Library
}
}
- ImageDimensions size = _imageProcessor.GetImageDimensions(item, image);
- image.Width = size.Width;
- image.Height = size.Height;
+ try
+ {
+ ImageDimensions size = _imageProcessor.GetImageDimensions(item, image);
+ image.Width = size.Width;
+ image.Height = size.Height;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Cannnot get image dimensions for {0}", image.Path);
+ image.Width = 0;
+ image.Height = 0;
+ continue;
+ }
try
{
@@ -2595,7 +2615,7 @@ namespace Emby.Server.Implementations.Library
Anime series don't generally have a season in their file name, however,
tvdb needs a season to correctly get the metadata.
Hence, a null season needs to be filled with something. */
- //FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified
+ // FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified
episode.ParentIndexNumber = 1;
}
@@ -2784,10 +2804,12 @@ namespace Emby.Server.Implementations.Library
{
throw new ArgumentNullException(nameof(path));
}
+
if (string.IsNullOrWhiteSpace(from))
{
throw new ArgumentNullException(nameof(from));
}
+
if (string.IsNullOrWhiteSpace(to))
{
throw new ArgumentNullException(nameof(to));
@@ -2861,7 +2883,6 @@ namespace Emby.Server.Implementations.Library
_logger.LogError(ex, "Error getting person");
return null;
}
-
}).Where(i => i != null).ToList();
}
@@ -2896,7 +2917,8 @@ namespace Emby.Server.Implementations.Library
}
catch (HttpException ex)
{
- if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
+ if (ex.StatusCode.HasValue
+ && (ex.StatusCode.Value == HttpStatusCode.NotFound || ex.StatusCode.Value == HttpStatusCode.Forbidden))
{
continue;
}
@@ -2989,21 +3011,6 @@ namespace Emby.Server.Implementations.Library
});
}
- private static bool ValidateNetworkPath(string path)
- {
- //if (Environment.OSVersion.Platform == PlatformID.Win32NT)
- //{
- // // We can't validate protocol-based paths, so just allow them
- // if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) == -1)
- // {
- // return Directory.Exists(path);
- // }
- //}
-
- // Without native support for unc, we cannot validate this when running under mono
- return true;
- }
-
private const string ShortcutFileExtension = ".mblink";
public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo)
@@ -3030,11 +3037,6 @@ namespace Emby.Server.Implementations.Library
throw new FileNotFoundException("The path does not exist.");
}
- if (!string.IsNullOrWhiteSpace(pathInfo.NetworkPath) && !ValidateNetworkPath(pathInfo.NetworkPath))
- {
- throw new FileNotFoundException("The network path does not exist.");
- }
-
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
@@ -3073,11 +3075,6 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(pathInfo));
}
- if (!string.IsNullOrWhiteSpace(pathInfo.NetworkPath) && !ValidateNetworkPath(pathInfo.NetworkPath))
- {
- throw new FileNotFoundException("The network path does not exist.");
- }
-
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
index ed7d8aa40..9b9f53049 100644
--- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs
+++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
@@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.Library
{
mediaInfo = _json.DeserializeFromFile<MediaInfo>(cacheFilePath);
- //_logger.LogDebug("Found cached media info");
+ // _logger.LogDebug("Found cached media info");
}
catch
{
@@ -85,7 +85,7 @@ namespace Emby.Server.Implementations.Library
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
_json.SerializeToFile(mediaInfo, cacheFilePath);
- //_logger.LogDebug("Saved media info to {0}", cacheFilePath);
+ // _logger.LogDebug("Saved media info to {0}", cacheFilePath);
}
}
@@ -148,17 +148,14 @@ namespace Emby.Server.Implementations.Library
{
videoStream.BitRate = 30000000;
}
-
else if (width >= 1900)
{
videoStream.BitRate = 20000000;
}
-
else if (width >= 1200)
{
videoStream.BitRate = 8000000;
}
-
else if (width >= 700)
{
videoStream.BitRate = 2000000;
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index 02add6600..ceb36b389 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -205,22 +205,27 @@ namespace Emby.Server.Implementations.Library
{
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;
@@ -436,7 +441,6 @@ namespace Emby.Server.Implementations.Library
}
return 1;
-
}).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0)
.ThenByDescending(i =>
{
@@ -620,7 +624,6 @@ namespace Emby.Server.Implementations.Library
MediaSource = mediaSource,
ExtractChapters = false,
MediaType = DlnaProfileType.Video
-
}, cancellationToken).ConfigureAwait(false);
mediaSource.MediaStreams = info.MediaStreams;
@@ -646,7 +649,7 @@ namespace Emby.Server.Implementations.Library
{
mediaInfo = _jsonSerializer.DeserializeFromFile<MediaInfo>(cacheFilePath);
- //_logger.LogDebug("Found cached media info");
+ // _logger.LogDebug("Found cached media info");
}
catch (Exception ex)
{
@@ -682,7 +685,7 @@ namespace Emby.Server.Implementations.Library
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
_jsonSerializer.SerializeToFile(mediaInfo, cacheFilePath);
- //_logger.LogDebug("Saved media info to {0}", cacheFilePath);
+ // _logger.LogDebug("Saved media info to {0}", cacheFilePath);
}
}
@@ -748,17 +751,14 @@ namespace Emby.Server.Implementations.Library
{
videoStream.BitRate = 30000000;
}
-
else if (width >= 1900)
{
videoStream.BitRate = 20000000;
}
-
else if (width >= 1200)
{
videoStream.BitRate = 8000000;
}
-
else if (width >= 700)
{
videoStream.BitRate = 2000000;
diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs
index 7ca15b4e5..4e4cac75b 100644
--- a/Emby.Server.Implementations/Library/ResolverHelper.cs
+++ b/Emby.Server.Implementations/Library/ResolverHelper.cs
@@ -107,7 +107,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
- /// Ensures DateCreated and DateModified have values
+ /// Ensures DateCreated and DateModified have values.
/// </summary>
/// <param name="fileSystem">The file system.</param>
/// <param name="item">The item.</param>
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
index fefc8e789..03059e6d3 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
@@ -209,8 +209,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
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()
+ // AdditionalParts = resolvedItem.Files.Skip(1).Select(i => i.Path).ToArray(),
+ // LocalAlternateVersions = resolvedItem.AlternateVersions.Select(i => i.Path).ToArray()
};
result.Items.Add(libraryItem);
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
index ebfe95d0a..79b6dded3 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
@@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
// Args points to an album if parent is an Artist folder or it directly contains music
if (args.IsDirectory)
{
- // if (args.Parent is MusicArtist) return true; //saves us from testing children twice
+ // if (args.Parent is MusicArtist) return true; // saves us from testing children twice
if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, _libraryManager))
{
return true;
diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
index fb75593bd..2f5e46038 100644
--- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
@@ -292,7 +292,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
}
return true;
- //var blurayExtensions = new[]
+ // var blurayExtensions = new[]
//{
// ".mts",
// ".m2ts",
@@ -300,7 +300,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
// ".mpls"
//};
- //return directoryService.GetFiles(fullPath).Any(i => blurayExtensions.Contains(i.Extension ?? string.Empty, StringComparer.OrdinalIgnoreCase));
+ // return directoryService.GetFiles(fullPath).Any(i => blurayExtensions.Contains(i.Extension ?? string.Empty, StringComparer.OrdinalIgnoreCase));
}
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
index 503de0b4e..86a5d8b7d 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
@@ -19,7 +19,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
// Only process items that are in a collection folder containing books
if (!string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase))
+ {
return null;
+ }
if (args.IsDirectory)
{
@@ -55,7 +57,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
// Don't return a Book if there is more (or less) than one document in the directory
if (bookFiles.Count != 1)
+ {
return null;
+ }
return new Book
{
diff --git a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs
index 32ccc7fdd..9ca76095b 100644
--- a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs
@@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
public virtual ResolverPriority Priority => ResolverPriority.First;
/// <summary>
- /// Sets initial values on the newly resolved item
+ /// Sets initial values on the newly resolved item.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>
diff --git a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
index 1030ed39d..99f304190 100644
--- a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
@@ -41,10 +41,12 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
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
+ return new UserRootFolder(); // if we got here and still a root - must be user root
}
+
if (args.IsVf)
{
return new CollectionFolder
@@ -73,7 +75,6 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
return false;
}
-
})
.Select(i => _fileSystem.GetFileNameWithoutExtension(i))
.FirstOrDefault();
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
index 7f477a0f0..2f7af60c0 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
@@ -55,6 +55,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
episode.SeriesId = series.Id;
episode.SeriesName = series.Name;
}
+
if (season != null)
{
episode.SeasonId = season.Id;
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
index 7f8800a64..c8e41001a 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
@@ -23,8 +23,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// </summary>
/// <param name="config">The config.</param>
/// <param name="libraryManager">The library manager.</param>
- /// <param name="localization">The localization</param>
- /// <param name="logger">The logger</param>
+ /// <param name="localization">The localization.</param>
+ /// <param name="logger">The logger.</param>
public SeasonResolver(
IServerConfigurationManager config,
ILibraryManager libraryManager,
@@ -94,7 +94,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
_localization.GetLocalizedString("NameSeasonNumber"),
seasonNumber,
args.GetLibraryOptions().PreferredMetadataLanguage);
-
}
return season;
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
index 2f206f74c..732bfd94d 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
@@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
var collectionType = args.GetCollectionType();
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
- //if (args.ContainsFileSystemEntryByName("tvshow.nfo"))
+ // if (args.ContainsFileSystemEntryByName("tvshow.nfo"))
//{
// return new Series
// {
diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs
index f37cb3e9d..3df9cc06f 100644
--- a/Emby.Server.Implementations/Library/SearchEngine.cs
+++ b/Emby.Server.Implementations/Library/SearchEngine.cs
@@ -194,6 +194,7 @@ namespace Emby.Server.Implementations.Library
{
searchQuery.AncestorIds = new[] { searchQuery.ParentId };
}
+
searchQuery.ParentId = Guid.Empty;
searchQuery.IncludeItemsByName = true;
searchQuery.IncludeItemTypes = Array.Empty<string>();
@@ -207,7 +208,6 @@ namespace Emby.Server.Implementations.Library
return mediaItems.Select(i => new SearchHintInfo
{
Item = i
-
}).ToList();
}
}
diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs
index 9e17fa672..175b3af57 100644
--- a/Emby.Server.Implementations/Library/UserDataManager.cs
+++ b/Emby.Server.Implementations/Library/UserDataManager.cs
@@ -103,7 +103,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
- /// Retrieve all user data for the given user
+ /// Retrieve all user data for the given user.
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
@@ -188,7 +188,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
- /// Converts a UserItemData to a DTOUserItemData
+ /// Converts a UserItemData to a DTOUserItemData.
/// </summary>
/// <param name="data">The data.</param>
/// <returns>DtoUserItemData.</returns>
diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
index 8a6bd5e78..d4c8c35e6 100644
--- a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
@@ -98,7 +98,6 @@ namespace Emby.Server.Implementations.Library.Validators
_libraryManager.DeleteItem(item, new DeleteOptions
{
DeleteFileLocation = false
-
}, false);
}
diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
index 7a6cd11df..ca35adfff 100644
--- a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
@@ -92,7 +92,6 @@ namespace Emby.Server.Implementations.Library.Validators
_libraryManager.DeleteItem(item, new DeleteOptions
{
DeleteFileLocation = false
-
}, false);
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 9fb5aec29..7b0fcbc9e 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -1547,7 +1547,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
IsFolder = false,
Recursive = true,
DtoOptions = new DtoOptions(true)
-
})
.Where(i => i.IsFileProtocol && File.Exists(i.Path))
.Skip(seriesTimer.KeepUpTo - 1)
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index 70dd8f321..d8ec107ec 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -183,7 +183,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var subtitleArgs = CopySubtitles ? " -codec:s copy" : " -sn";
- //var outputParam = string.Equals(Path.GetExtension(targetFile), ".mp4", StringComparison.OrdinalIgnoreCase) ?
+ // var outputParam = string.Equals(Path.GetExtension(targetFile), ".mp4", StringComparison.OrdinalIgnoreCase) ?
// " -f mp4 -movflags frag_keyframe+empty_moov" :
// string.Empty;
@@ -206,13 +206,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
return "-codec:a:0 copy";
- //var audioChannels = 2;
- //var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
- //if (audioStream != null)
+ // var audioChannels = 2;
+ // var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
+ // if (audioStream != null)
//{
// audioChannels = audioStream.Channels ?? audioChannels;
//}
- //return "-codec:a:0 aac -strict experimental -ab 320000";
+ // return "-codec:a:0 aac -strict experimental -ab 320000";
}
private static bool EncodeVideo(MediaSourceInfo mediaSource)
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
index 0b0ff6cb3..142c59542 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
@@ -56,7 +56,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
name += " " + info.EpisodeTitle;
}
}
-
else if (info.IsMovie && info.ProductionYear != null)
{
name += " (" + info.ProductionYear + ")";
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index a65b3ee03..3709f8fe4 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -145,7 +145,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
var programsInfo = new List<ProgramInfo>();
foreach (ScheduleDirect.Program schedule in dailySchedules.SelectMany(d => d.programs))
{
- //_logger.LogDebug("Proccesing Schedule for statio ID " + stationID +
+ // _logger.LogDebug("Proccesing Schedule for statio ID " + stationID +
// " which corresponds to channel " + channelNumber + " and program id " +
// schedule.programID + " which says it has images? " +
// programDict[schedule.programID].hasImageArtwork);
@@ -178,7 +178,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
programEntry.backdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect);
- //programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
+ // programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
// GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
// GetProgramImage(ApiUrl, data, "Banner-LO", false) ??
// GetProgramImage(ApiUrl, data, "Banner-LOT", false);
@@ -212,6 +212,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
channelNumber = map.channel;
}
+
if (string.IsNullOrWhiteSpace(channelNumber))
{
channelNumber = map.atscMajor + "." + map.atscMinor;
@@ -276,7 +277,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
CommunityRating = null,
EpisodeTitle = episodeTitle,
Audio = audioType,
- //IsNew = programInfo.@new ?? false,
+ // IsNew = programInfo.@new ?? false,
IsRepeat = programInfo.@new == null,
IsSeries = string.Equals(details.entityType, "episode", StringComparison.OrdinalIgnoreCase),
ImageUrl = details.primaryImage,
@@ -400,6 +401,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
date = DateTime.SpecifyKind(date, DateTimeKind.Utc);
}
+
return date;
}
@@ -622,6 +624,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
_lastErrorResponse = DateTime.UtcNow;
}
}
+
throw;
}
finally
@@ -701,7 +704,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
CancellationToken = cancellationToken,
LogErrorResponseBody = true
};
- //_logger.LogInformation("Obtaining token from Schedules Direct from addres: " + httpOptions.Url + " with body " +
+ // _logger.LogInformation("Obtaining token from Schedules Direct from addres: " + httpOptions.Url + " with body " +
// httpOptions.RequestContent);
using (var response = await Post(httpOptions, false, null).ConfigureAwait(false))
@@ -805,11 +808,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
throw new ArgumentException("Username is required");
}
+
if (string.IsNullOrEmpty(info.Password))
{
throw new ArgumentException("Password is required");
}
}
+
if (validateListings)
{
if (string.IsNullOrEmpty(info.ListingsId))
@@ -932,24 +937,35 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public class Token
{
public int code { get; set; }
+
public string message { get; set; }
+
public string serverID { get; set; }
+
public string token { get; set; }
}
+
public class Lineup
{
public string lineup { get; set; }
+
public string name { get; set; }
+
public string transport { get; set; }
+
public string location { get; set; }
+
public string uri { get; set; }
}
public class Lineups
{
public int code { get; set; }
+
public string serverID { get; set; }
+
public string datetime { get; set; }
+
public List<Lineup> lineups { get; set; }
}
@@ -957,8 +973,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public class Headends
{
public string headend { get; set; }
+
public string transport { get; set; }
+
public string location { get; set; }
+
public List<Lineup> lineups { get; set; }
}
@@ -967,59 +986,83 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public class Map
{
public string stationID { get; set; }
+
public string channel { get; set; }
+
public string logicalChannelNumber { get; set; }
+
public int uhfVhf { get; set; }
+
public int atscMajor { get; set; }
+
public int atscMinor { get; set; }
}
public class Broadcaster
{
public string city { get; set; }
+
public string state { get; set; }
+
public string postalcode { get; set; }
+
public string country { get; set; }
}
public class Logo
{
public string URL { get; set; }
+
public int height { get; set; }
+
public int width { get; set; }
+
public string md5 { get; set; }
}
public class Station
{
public string stationID { get; set; }
+
public string name { get; set; }
+
public string callsign { get; set; }
+
public List<string> broadcastLanguage { get; set; }
+
public List<string> descriptionLanguage { get; set; }
+
public Broadcaster broadcaster { get; set; }
+
public string affiliate { get; set; }
+
public Logo logo { get; set; }
+
public bool? isCommercialFree { get; set; }
}
public class Metadata
{
public string lineup { get; set; }
+
public string modified { get; set; }
+
public string transport { get; set; }
}
public class Channel
{
public List<Map> map { get; set; }
+
public List<Station> stations { get; set; }
+
public Metadata metadata { get; set; }
}
public class RequestScheduleForChannel
{
public string stationID { get; set; }
+
public List<string> date { get; set; }
}
@@ -1029,29 +1072,43 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public class Rating
{
public string body { get; set; }
+
public string code { get; set; }
}
public class Multipart
{
public int partNumber { get; set; }
+
public int totalParts { get; set; }
}
public class Program
{
public string programID { get; set; }
+
public string airDateTime { get; set; }
+
public int duration { get; set; }
+
public string md5 { get; set; }
+
public List<string> audioProperties { get; set; }
+
public List<string> videoProperties { get; set; }
+
public List<Rating> ratings { get; set; }
+
public bool? @new { get; set; }
+
public Multipart multipart { get; set; }
+
public string liveTapeDelay { get; set; }
+
public bool premiere { get; set; }
+
public bool repeat { get; set; }
+
public string isPremiereOrFinale { get; set; }
}
@@ -1060,16 +1117,22 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public class MetadataSchedule
{
public string modified { get; set; }
+
public string md5 { get; set; }
+
public string startDate { get; set; }
+
public string endDate { get; set; }
+
public int days { get; set; }
}
public class Day
{
public string stationID { get; set; }
+
public List<Program> programs { get; set; }
+
public MetadataSchedule metadata { get; set; }
public Day()
@@ -1092,24 +1155,28 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public class Description100
{
public string descriptionLanguage { get; set; }
+
public string description { get; set; }
}
public class Description1000
{
public string descriptionLanguage { get; set; }
+
public string description { get; set; }
}
public class DescriptionsProgram
{
public List<Description100> description100 { get; set; }
+
public List<Description1000> description1000 { get; set; }
}
public class Gracenote
{
public int season { get; set; }
+
public int episode { get; set; }
}
@@ -1121,104 +1188,154 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public class ContentRating
{
public string body { get; set; }
+
public string code { get; set; }
}
public class Cast
{
public string billingOrder { get; set; }
+
public string role { get; set; }
+
public string nameId { get; set; }
+
public string personId { get; set; }
+
public string name { get; set; }
+
public string characterName { get; set; }
}
public class Crew
{
public string billingOrder { get; set; }
+
public string role { get; set; }
+
public string nameId { get; set; }
+
public string personId { get; set; }
+
public string name { get; set; }
}
public class QualityRating
{
public string ratingsBody { get; set; }
+
public string rating { get; set; }
+
public string minRating { get; set; }
+
public string maxRating { get; set; }
+
public string increment { get; set; }
}
public class Movie
{
public string year { get; set; }
+
public int duration { get; set; }
+
public List<QualityRating> qualityRating { get; set; }
}
public class Recommendation
{
public string programID { get; set; }
+
public string title120 { get; set; }
}
public class ProgramDetails
{
public string audience { get; set; }
+
public string programID { get; set; }
+
public List<Title> titles { get; set; }
+
public EventDetails eventDetails { get; set; }
+
public DescriptionsProgram descriptions { get; set; }
+
public string originalAirDate { get; set; }
+
public List<string> genres { get; set; }
+
public string episodeTitle150 { get; set; }
+
public List<MetadataPrograms> metadata { get; set; }
+
public List<ContentRating> contentRating { get; set; }
+
public List<Cast> cast { get; set; }
+
public List<Crew> crew { get; set; }
+
public string entityType { get; set; }
+
public string showType { get; set; }
+
public bool hasImageArtwork { get; set; }
+
public string primaryImage { get; set; }
+
public string thumbImage { get; set; }
+
public string backdropImage { get; set; }
+
public string bannerImage { get; set; }
+
public string imageID { get; set; }
+
public string md5 { get; set; }
+
public List<string> contentAdvisory { get; set; }
+
public Movie movie { get; set; }
+
public List<Recommendation> recommendations { get; set; }
}
public class Caption
{
public string content { get; set; }
+
public string lang { get; set; }
}
public class ImageData
{
public string width { get; set; }
+
public string height { get; set; }
+
public string uri { get; set; }
+
public string size { get; set; }
+
public string aspect { get; set; }
+
public string category { get; set; }
+
public string text { get; set; }
+
public string primary { get; set; }
+
public string tier { get; set; }
+
public Caption caption { get; set; }
}
public class ShowImages
{
public string programID { get; set; }
+
public List<ImageData> data { get; set; }
}
-
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
index 077b5c7e5..0a93c4674 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
@@ -224,6 +224,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
uniqueString = "-" + programInfo.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture);
}
+
if (programInfo.EpisodeNumber.HasValue)
{
uniqueString = "-" + programInfo.EpisodeNumber.Value.ToString(CultureInfo.InvariantCulture);
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index e39092f97..4c1de3bcc 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -406,8 +406,8 @@ namespace Emby.Server.Implementations.LiveTv
if (!(service is EmbyTV.EmbyTV))
{
// We can't trust that we'll be able to direct stream it through emby server, no matter what the provider says
- //mediaSource.SupportsDirectPlay = false;
- //mediaSource.SupportsDirectStream = false;
+ // mediaSource.SupportsDirectPlay = false;
+ // mediaSource.SupportsDirectStream = false;
mediaSource.SupportsTranscoding = true;
foreach (var stream in mediaSource.MediaStreams)
{
@@ -556,9 +556,10 @@ namespace Emby.Server.Implementations.LiveTv
{
forceUpdate = true;
}
+
item.ParentId = channel.Id;
- //item.ChannelType = channelType;
+ // item.ChannelType = channelType;
item.Audio = info.Audio;
item.ChannelId = channel.Id;
@@ -575,6 +576,7 @@ namespace Emby.Server.Implementations.LiveTv
{
forceUpdate = true;
}
+
item.ExternalSeriesId = seriesId;
var isSeries = info.IsSeries || !string.IsNullOrEmpty(info.EpisodeTitle);
@@ -589,30 +591,37 @@ namespace Emby.Server.Implementations.LiveTv
{
tags.Add("Live");
}
+
if (info.IsPremiere)
{
tags.Add("Premiere");
}
+
if (info.IsNews)
{
tags.Add("News");
}
+
if (info.IsSports)
{
tags.Add("Sports");
}
+
if (info.IsKids)
{
tags.Add("Kids");
}
+
if (info.IsRepeat)
{
tags.Add("Repeat");
}
+
if (info.IsMovie)
{
tags.Add("Movie");
}
+
if (isSeries)
{
tags.Add("Series");
@@ -635,6 +644,7 @@ namespace Emby.Server.Implementations.LiveTv
{
forceUpdate = true;
}
+
item.IsSeries = isSeries;
item.Name = info.Name;
@@ -652,12 +662,14 @@ namespace Emby.Server.Implementations.LiveTv
{
forceUpdate = true;
}
+
item.StartDate = info.StartDate;
if (item.EndDate != info.EndDate)
{
forceUpdate = true;
}
+
item.EndDate = info.EndDate;
item.ProductionYear = info.ProductionYear;
@@ -1168,7 +1180,6 @@ namespace Emby.Server.Implementations.LiveTv
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
ChannelIds = new Guid[] { currentChannel.Id },
DtoOptions = new DtoOptions(true)
-
}).Cast<LiveTvProgram>().ToDictionary(i => i.Id);
var newPrograms = new List<LiveTvProgram>();
@@ -1368,10 +1379,10 @@ namespace Emby.Server.Implementations.LiveTv
// limit = (query.Limit ?? 10) * 2;
limit = null;
- //var allActivePaths = EmbyTV.EmbyTV.Current.GetAllActiveRecordings().Select(i => i.Path).ToArray();
- //var items = allActivePaths.Select(i => _libraryManager.FindByPath(i, false)).Where(i => i != null).ToArray();
+ // var allActivePaths = EmbyTV.EmbyTV.Current.GetAllActiveRecordings().Select(i => i.Path).ToArray();
+ // var items = allActivePaths.Select(i => _libraryManager.FindByPath(i, false)).Where(i => i != null).ToArray();
- //return new QueryResult<BaseItem>
+ // return new QueryResult<BaseItem>
//{
// Items = items,
// TotalRecordCount = items.Length
@@ -1738,7 +1749,6 @@ namespace Emby.Server.Implementations.LiveTv
var results = await GetTimers(new TimerQuery
{
Id = id
-
}, cancellationToken).ConfigureAwait(false);
return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
@@ -1790,7 +1800,6 @@ namespace Emby.Server.Implementations.LiveTv
.Select(i =>
{
return i.Item1;
-
})
.ToArray();
@@ -1845,7 +1854,6 @@ namespace Emby.Server.Implementations.LiveTv
}
return _tvDtoService.GetSeriesTimerInfoDto(i.Item1, i.Item2, channelName);
-
})
.ToArray();
@@ -1878,7 +1886,6 @@ namespace Emby.Server.Implementations.LiveTv
OrderBy = new[] { (ItemSortBy.StartDate, SortOrder.Ascending) },
TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Id },
DtoOptions = options
-
}) : new List<BaseItem>();
RemoveFields(options);
@@ -1956,7 +1963,7 @@ namespace Emby.Server.Implementations.LiveTv
OriginalAirDate = program.PremiereDate,
Overview = program.Overview,
StartDate = program.StartDate,
- //ImagePath = program.ExternalImagePath,
+ // ImagePath = program.ExternalImagePath,
Name = program.Name,
OfficialRating = program.OfficialRating
};
@@ -2456,7 +2463,6 @@ namespace Emby.Server.Implementations.LiveTv
UserId = user.Id,
IsRecordingsFolder = true,
RefreshLatestChannelItems = refreshChannels
-
}).Items);
return folders.Cast<BaseItem>().ToList();
diff --git a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
index 8e7d60a15..f1b61f7c7 100644
--- a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
+++ b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
@@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.LiveTv
}
/// <summary>
- /// Creates the triggers that define when the task will run
+ /// Creates the triggers that define when the task will run.
/// </summary>
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
index ba3594efd..a8d34d19c 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
@@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var result = await GetChannelsInternal(tuner, cancellationToken).ConfigureAwait(false);
var list = result.ToList();
- //logger.LogInformation("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list));
+ // logger.LogInformation("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list));
if (!string.IsNullOrEmpty(key) && list.Count > 0)
{
@@ -99,7 +99,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
catch (IOException)
{
-
}
}
}
@@ -116,7 +115,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
catch (IOException)
{
-
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 25b2c674c..2e2488e6e 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -111,7 +111,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
ChannelType = ChannelType.TV,
IsLegacyTuner = (i.URL ?? string.Empty).StartsWith("hdhomerun", StringComparison.OrdinalIgnoreCase),
Path = i.URL
-
}).Cast<ChannelInfo>().ToList();
}
@@ -171,6 +170,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_modelCache[cacheKey] = response;
}
}
+
return response;
}
@@ -201,7 +201,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
var name = line.Substring(0, index - 1);
var currentChannel = line.Substring(index + 7);
- if (currentChannel != "none") { status = LiveTvTunerStatus.LiveTv; } else { status = LiveTvTunerStatus.Available; }
+ if (currentChannel != "none")
+ {
+ status = LiveTvTunerStatus.LiveTv;
+ }
+ else
+ {
+ status = LiveTvTunerStatus.Available;
+ }
+
tuners.Add(new LiveTvTunerInfo
{
Name = name,
@@ -230,11 +238,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
inside = true;
continue;
}
+
if (let == '>')
{
inside = false;
continue;
}
+
if (!inside)
{
buffer[bufferIndex] = let;
@@ -332,12 +342,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private class Channels
{
public string GuideNumber { get; set; }
+
public string GuideName { get; set; }
+
public string VideoCodec { get; set; }
+
public string AudioCodec { get; set; }
+
public string URL { get; set; }
+
public bool Favorite { get; set; }
+
public bool DRM { get; set; }
+
public int HD { get; set; }
}
@@ -481,7 +498,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
Height = height,
BitRate = videoBitrate,
NalLengthSize = nal
-
},
new MediaStream
{
@@ -502,8 +518,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
SupportsTranscoding = true,
IsInfiniteStream = true,
IgnoreDts = true,
- //IgnoreIndex = true,
- //ReadAtNativeFramerate = true
+ // IgnoreIndex = true,
+ // ReadAtNativeFramerate = true
};
mediaSource.InferTotalBitrate();
@@ -659,13 +675,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public class DiscoverResponse
{
public string FriendlyName { get; set; }
+
public string ModelNumber { get; set; }
+
public string FirmwareName { get; set; }
+
public string FirmwareVersion { get; set; }
+
public string DeviceID { get; set; }
+
public string DeviceAuth { get; set; }
+
public string BaseURL { get; set; }
+
public string LineupURL { get; set; }
+
public int TunerCount { get; set; }
public bool SupportsTranscoding
@@ -674,7 +698,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
var model = ModelNumber ?? string.Empty;
- if ((model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1))
+ if (model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1)
{
return true;
}
@@ -722,7 +746,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
}
}
-
}
catch (OperationCanceledException)
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
index 82b1f3cf1..6730751d5 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
@@ -117,17 +117,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
taskCompletionSource,
LiveStreamCancellationTokenSource.Token).ConfigureAwait(false);
- //OpenedMediaSource.Protocol = MediaProtocol.File;
- //OpenedMediaSource.Path = tempFile;
- //OpenedMediaSource.ReadAtNativeFramerate = true;
+ // OpenedMediaSource.Protocol = MediaProtocol.File;
+ // OpenedMediaSource.Path = tempFile;
+ // OpenedMediaSource.ReadAtNativeFramerate = true;
MediaSource.Path = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
MediaSource.Protocol = MediaProtocol.Http;
- //OpenedMediaSource.SupportsDirectPlay = false;
- //OpenedMediaSource.SupportsDirectStream = true;
- //OpenedMediaSource.SupportsTranscoding = true;
+ // OpenedMediaSource.SupportsDirectPlay = false;
+ // OpenedMediaSource.SupportsDirectStream = true;
+ // OpenedMediaSource.SupportsTranscoding = true;
- //await Task.Delay(5000).ConfigureAwait(false);
+ // await Task.Delay(5000).ConfigureAwait(false);
await taskCompletionSource.Task.ConfigureAwait(false);
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
index 4e4f1d7f6..0333e723b 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
@@ -58,12 +58,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
protected virtual int EmptyReadLimit => 1000;
public MediaSourceInfo OriginalMediaSource { get; set; }
+
public MediaSourceInfo MediaSource { get; set; }
public int ConsumerCount { get; set; }
public string OriginalStreamId { get; set; }
+
public bool EnableStreamSharing { get; set; }
+
public string UniqueId { get; }
public string TunerHostId { get; }
@@ -220,11 +223,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
catch (IOException)
{
-
}
catch (ArgumentException)
{
-
}
catch (Exception ex)
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index f7c9c736e..ff42a9747 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -127,7 +127,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
using (var stream = await new M3uParser(Logger, _httpClient, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false))
{
-
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
index 59451fccd..c798c0a85 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
@@ -210,7 +210,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
}
}
-
}
if (!IsValidChannelNumber(numberString))
@@ -284,7 +283,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number))
{
- //channel.Number = number.ToString();
+ // channel.Number = number.ToString();
nameInExtInf = nameInExtInf.Substring(numberIndex + 1).Trim(new[] { ' ', '-' });
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
index 322fbbbaa..bc4dcd894 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
@@ -103,21 +103,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
_ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
- //OpenedMediaSource.Protocol = MediaProtocol.File;
- //OpenedMediaSource.Path = tempFile;
- //OpenedMediaSource.ReadAtNativeFramerate = true;
+ // OpenedMediaSource.Protocol = MediaProtocol.File;
+ // OpenedMediaSource.Path = tempFile;
+ // OpenedMediaSource.ReadAtNativeFramerate = true;
MediaSource.Path = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
MediaSource.Protocol = MediaProtocol.Http;
- //OpenedMediaSource.Path = TempFilePath;
- //OpenedMediaSource.Protocol = MediaProtocol.File;
+ // OpenedMediaSource.Path = TempFilePath;
+ // OpenedMediaSource.Protocol = MediaProtocol.File;
- //OpenedMediaSource.Path = _tempFilePath;
- //OpenedMediaSource.Protocol = MediaProtocol.File;
- //OpenedMediaSource.SupportsDirectPlay = false;
- //OpenedMediaSource.SupportsDirectStream = true;
- //OpenedMediaSource.SupportsTranscoding = true;
+ // OpenedMediaSource.Path = _tempFilePath;
+ // OpenedMediaSource.Protocol = MediaProtocol.File;
+ // OpenedMediaSource.SupportsDirectPlay = false;
+ // OpenedMediaSource.SupportsDirectStream = true;
+ // OpenedMediaSource.SupportsTranscoding = true;
await taskCompletionSource.Task.ConfigureAwait(false);
if (taskCompletionSource.Task.Exception != null)
{
diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json
index 20447347b..e587c37d5 100644
--- a/Emby.Server.Implementations/Localization/Core/af.json
+++ b/Emby.Server.Implementations/Localization/Core/af.json
@@ -19,8 +19,8 @@
"Sync": "Sinkroniseer",
"HeaderFavoriteSongs": "Gunsteling Liedjies",
"Songs": "Liedjies",
- "DeviceOnlineWithName": "{0} is verbind",
- "DeviceOfflineWithName": "{0} het afgesluit",
+ "DeviceOnlineWithName": "{0} gekoppel is",
+ "DeviceOfflineWithName": "{0} is ontkoppel",
"Collections": "Versamelings",
"Inherit": "Ontvang",
"HeaderLiveTV": "Live TV",
@@ -91,5 +91,9 @@
"ChapterNameValue": "Hoofstuk",
"CameraImageUploadedFrom": "'n Nuwe kamera photo opgelaai van {0}",
"AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer",
- "Albums": "Albums"
+ "Albums": "Albums",
+ "TasksChannelsCategory": "Internet kanale",
+ "TasksApplicationCategory": "aansoek",
+ "TasksLibraryCategory": "biblioteek",
+ "TasksMaintenanceCategory": "onderhoud"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json
index d68928fce..4eac8e75d 100644
--- a/Emby.Server.Implementations/Localization/Core/ar.json
+++ b/Emby.Server.Implementations/Localization/Core/ar.json
@@ -1,5 +1,5 @@
{
- "Albums": "ألبومات",
+ "Albums": "البومات",
"AppDeviceValues": "تطبيق: {0}, جهاز: {1}",
"Application": "تطبيق",
"Artists": "الفنانين",
@@ -14,7 +14,7 @@
"FailedLoginAttemptWithUserName": "عملية تسجيل الدخول فشلت من {0}",
"Favorites": "المفضلة",
"Folders": "المجلدات",
- "Genres": "الأنواع",
+ "Genres": "التضنيفات",
"HeaderAlbumArtists": "فناني الألبومات",
"HeaderCameraUploads": "تحميلات الكاميرا",
"HeaderContinueWatching": "استئناف",
@@ -50,7 +50,7 @@
"NotificationOptionAudioPlayback": "بدأ تشغيل المقطع الصوتي",
"NotificationOptionAudioPlaybackStopped": "تم إيقاف تشغيل المقطع الصوتي",
"NotificationOptionCameraImageUploaded": "تم رفع صورة الكاميرا",
- "NotificationOptionInstallationFailed": "فشل في التثبيت",
+ "NotificationOptionInstallationFailed": "فشل التثبيت",
"NotificationOptionNewLibraryContent": "تم إضافة محتوى جديد",
"NotificationOptionPluginError": "فشل في البرنامج المضاف",
"NotificationOptionPluginInstalled": "تم تثبيت الملحق",
diff --git a/Emby.Server.Implementations/Localization/Core/bn.json b/Emby.Server.Implementations/Localization/Core/bn.json
index 77007845f..1f309f3ff 100644
--- a/Emby.Server.Implementations/Localization/Core/bn.json
+++ b/Emby.Server.Implementations/Localization/Core/bn.json
@@ -62,13 +62,13 @@
"NotificationOptionPluginInstalled": "প্লাগিন ইন্সটল করা হয়েছে",
"NotificationOptionPluginError": "প্লাগিন ব্যর্থ",
"NotificationOptionNewLibraryContent": "নতুন কন্টেন্ট যোগ করা হয়েছে",
- "NotificationOptionInstallationFailed": "ইন্সটল ব্যর্থ",
+ "NotificationOptionInstallationFailed": "ইন্সটল ব্যর্থ হয়েছে",
"NotificationOptionCameraImageUploaded": "ক্যামেরার ছবি আপলোড হয়েছে",
"NotificationOptionAudioPlaybackStopped": "গান বাজা বন্ধ হয়েছে",
"NotificationOptionAudioPlayback": "গান বাজা শুরু হয়েছে",
"NotificationOptionApplicationUpdateInstalled": "এপ্লিকেশনের আপডেট ইনস্টল করা হয়েছে",
"NotificationOptionApplicationUpdateAvailable": "এপ্লিকেশনের আপডেট রয়েছে",
- "NewVersionIsAvailable": "জেলিফিন সার্ভারের একটি নতুন ভার্শন ডাউনলোডের জন্য তৈরী",
+ "NewVersionIsAvailable": "জেলিফিন সার্ভারের একটি নতুন ভার্শন ডাউনলোডের জন্য তৈরী।",
"NameSeasonUnknown": "সিজন অজানা",
"NameSeasonNumber": "সিজন {0}",
"NameInstallFailed": "{0} ইন্সটল ব্যর্থ",
@@ -100,5 +100,18 @@
"TaskCleanCacheDescription": "সিস্টেমে আর প্রয়োজন নেই ক্যাশ, ফাইলগুলি মুছে ফেলুন।",
"TaskCleanCache": "ক্লিন ক্যাশ ডিরেক্টরি",
"TasksChannelsCategory": "ইন্টারনেট চ্যানেল",
- "TasksApplicationCategory": "আবেদন"
+ "TasksApplicationCategory": "আবেদন",
+ "TaskDownloadMissingSubtitlesDescription": "মেটাডেটা কনফিগারেশনের উপর ভিত্তি করে অনুপস্থিত সাবটাইটেলগুলির জন্য ইন্টারনেট অনুসন্ধান করে।",
+ "TaskDownloadMissingSubtitles": "অনুপস্থিত সাবটাইটেলগুলি ডাউনলোড করুন",
+ "TaskRefreshChannelsDescription": "ইন্টারনেট চ্যানেল তথ্য রিফ্রেশ করুন।",
+ "TaskRefreshChannels": "চ্যানেল রিফ্রেশ করুন",
+ "TaskCleanTranscodeDescription": "এক দিনেরও বেশি পুরানো ট্রান্সকোড ফাইলগুলি মুছে ফেলুন।",
+ "TaskCleanTranscode": "ট্রান্সকোড ডিরেক্টরি ক্লিন করুন",
+ "TaskUpdatePluginsDescription": "স্বয়ংক্রিয়ভাবে আপডেট কনফিগার করা প্লাগইনগুলির জন্য আপডেট ডাউনলোড এবং ইনস্টল করুন।",
+ "TaskUpdatePlugins": "প্লাগইন আপডেট করুন",
+ "TaskRefreshPeopleDescription": "আপনার মিডিয়া লাইব্রেরিতে অভিনেতা এবং পরিচালকদের জন্য মেটাডাটা আপডেট করুন।",
+ "TaskRefreshPeople": "পিপল রিফ্রেশ করুন",
+ "TaskCleanLogsDescription": "{0} দিনের বেশী পুরানো লগ ফাইলগুলি মুছে ফেলুন।",
+ "TaskCleanLogs": "লগ ডিরেক্টরি ক্লিন করুন",
+ "TaskRefreshLibraryDescription": "নতুন ফাইলের জন্য মিডিয়া লাইব্রেরি স্ক্যান এবং মেটাডাটা রিফ্রেশ করুন।"
}
diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json
index 82df43be1..a6e9779c9 100644
--- a/Emby.Server.Implementations/Localization/Core/de.json
+++ b/Emby.Server.Implementations/Localization/Core/de.json
@@ -3,7 +3,7 @@
"AppDeviceValues": "App: {0}, Gerät: {1}",
"Application": "Anwendung",
"Artists": "Interpreten",
- "AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich authentifiziert",
+ "AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich angemeldet",
"Books": "Bücher",
"CameraImageUploadedFrom": "Ein neues Foto wurde von {0} hochgeladen",
"Channels": "Kanäle",
diff --git a/Emby.Server.Implementations/Localization/Core/es-AR.json b/Emby.Server.Implementations/Localization/Core/es-AR.json
index fc9a10f27..ac96c788c 100644
--- a/Emby.Server.Implementations/Localization/Core/es-AR.json
+++ b/Emby.Server.Implementations/Localization/Core/es-AR.json
@@ -20,7 +20,7 @@
"HeaderContinueWatching": "Seguir viendo",
"HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos",
- "HeaderFavoriteEpisodes": "Episodios favoritos",
+ "HeaderFavoriteEpisodes": "Capítulos favoritos",
"HeaderFavoriteShows": "Programas favoritos",
"HeaderFavoriteSongs": "Canciones favoritas",
"HeaderLiveTV": "TV en vivo",
diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json
index 20b37ec9f..4ba324aa1 100644
--- a/Emby.Server.Implementations/Localization/Core/es-MX.json
+++ b/Emby.Server.Implementations/Localization/Core/es-MX.json
@@ -31,7 +31,7 @@
"ItemAddedWithName": "{0} fue agregado a la biblioteca",
"ItemRemovedWithName": "{0} fue removido de la biblioteca",
"LabelIpAddressValue": "Dirección IP: {0}",
- "LabelRunningTimeValue": "Duración: {0}",
+ "LabelRunningTimeValue": "Tiempo de reproducción: {0}",
"Latest": "Recientes",
"MessageApplicationUpdated": "El servidor Jellyfin ha sido actualizado",
"MessageApplicationUpdatedTo": "El servidor Jellyfin ha sido actualizado a {0}",
diff --git a/Emby.Server.Implementations/Localization/Core/es_419.json b/Emby.Server.Implementations/Localization/Core/es_419.json
index b0fdc8386..0959ef2ca 100644
--- a/Emby.Server.Implementations/Localization/Core/es_419.json
+++ b/Emby.Server.Implementations/Localization/Core/es_419.json
@@ -1,5 +1,5 @@
{
- "LabelRunningTimeValue": "Duración: {0}",
+ "LabelRunningTimeValue": "Tiempo en ejecución: {0}",
"ValueSpecialEpisodeName": "Especial - {0}",
"Sync": "Sincronizar",
"Songs": "Canciones",
diff --git a/Emby.Server.Implementations/Localization/Core/fr-CA.json b/Emby.Server.Implementations/Localization/Core/fr-CA.json
index 3dcfa6844..cd1c8144f 100644
--- a/Emby.Server.Implementations/Localization/Core/fr-CA.json
+++ b/Emby.Server.Implementations/Localization/Core/fr-CA.json
@@ -109,9 +109,10 @@
"TaskCleanLogs": "Nettoyer le répertoire des journaux",
"TaskRefreshLibraryDescription": "Analyse votre bibliothèque média 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",
+ "TaskRefreshChapterImagesDescription": "Créer des vignettes pour les vidéos qui ont des chapitres.",
"TaskRefreshLibrary": "Analyser la bibliothèque de médias",
"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."
+ "TaskCleanCacheDescription": "Supprime les fichiers temporaires qui ne sont plus nécessaire pour le système.",
+ "TasksChannelsCategory": "Canaux Internet"
}
diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json
index c169a35e7..97c77017b 100644
--- a/Emby.Server.Implementations/Localization/Core/hr.json
+++ b/Emby.Server.Implementations/Localization/Core/hr.json
@@ -5,23 +5,23 @@
"Artists": "Izvođači",
"AuthenticationSucceededWithUserName": "{0} uspješno ovjerena",
"Books": "Knjige",
- "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
+ "CameraImageUploadedFrom": "Nova fotografija sa kamere je uploadana iz {0}",
"Channels": "Kanali",
"ChapterNameValue": "Poglavlje {0}",
"Collections": "Kolekcije",
"DeviceOfflineWithName": "{0} se odspojilo",
"DeviceOnlineWithName": "{0} je spojeno",
"FailedLoginAttemptWithUserName": "Neuspjeli pokušaj prijave za {0}",
- "Favorites": "Omiljeni",
+ "Favorites": "Favoriti",
"Folders": "Mape",
"Genres": "Žanrovi",
- "HeaderAlbumArtists": "Izvođači albuma",
- "HeaderCameraUploads": "Camera Uploads",
- "HeaderContinueWatching": "Continue Watching",
+ "HeaderAlbumArtists": "Izvođači na albumu",
+ "HeaderCameraUploads": "Uvoz sa kamere",
+ "HeaderContinueWatching": "Nastavi gledati",
"HeaderFavoriteAlbums": "Omiljeni albumi",
"HeaderFavoriteArtists": "Omiljeni izvođači",
"HeaderFavoriteEpisodes": "Omiljene epizode",
- "HeaderFavoriteShows": "Omiljene emisije",
+ "HeaderFavoriteShows": "Omiljene serije",
"HeaderFavoriteSongs": "Omiljene pjesme",
"HeaderLiveTV": "TV uživo",
"HeaderNextUp": "Sljedeće je",
@@ -34,23 +34,23 @@
"LabelRunningTimeValue": "Vrijeme rada: {0}",
"Latest": "Najnovije",
"MessageApplicationUpdated": "Jellyfin Server je ažuriran",
- "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
+ "MessageApplicationUpdatedTo": "Jellyfin Server je ažuriran na {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Odjeljak postavka servera {0} je ažuriran",
"MessageServerConfigurationUpdated": "Postavke servera su ažurirane",
"MixedContent": "Miješani sadržaj",
"Movies": "Filmovi",
"Music": "Glazba",
"MusicVideos": "Glazbeni spotovi",
- "NameInstallFailed": "{0} installation failed",
+ "NameInstallFailed": "{0} neuspješnih instalacija",
"NameSeasonNumber": "Sezona {0}",
- "NameSeasonUnknown": "Season Unknown",
- "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
+ "NameSeasonUnknown": "Nepoznata sezona",
+ "NewVersionIsAvailable": "Nova verzija Jellyfin servera je dostupna za preuzimanje.",
"NotificationOptionApplicationUpdateAvailable": "Dostupno ažuriranje aplikacije",
"NotificationOptionApplicationUpdateInstalled": "Instalirano ažuriranje aplikacije",
"NotificationOptionAudioPlayback": "Reprodukcija glazbe započeta",
"NotificationOptionAudioPlaybackStopped": "Reprodukcija audiozapisa je zaustavljena",
"NotificationOptionCameraImageUploaded": "Slike kamere preuzete",
- "NotificationOptionInstallationFailed": "Instalacija nije izvršena",
+ "NotificationOptionInstallationFailed": "Instalacija neuspješna",
"NotificationOptionNewLibraryContent": "Novi sadržaj je dodan",
"NotificationOptionPluginError": "Dodatak otkazao",
"NotificationOptionPluginInstalled": "Dodatak instaliran",
@@ -62,7 +62,7 @@
"NotificationOptionVideoPlayback": "Reprodukcija videa započeta",
"NotificationOptionVideoPlaybackStopped": "Reprodukcija videozapisa je zaustavljena",
"Photos": "Slike",
- "Playlists": "Popisi",
+ "Playlists": "Popis za reprodukciju",
"Plugin": "Dodatak",
"PluginInstalledWithName": "{0} je instalirano",
"PluginUninstalledWithName": "{0} je deinstalirano",
@@ -70,15 +70,15 @@
"ProviderValue": "Pružitelj: {0}",
"ScheduledTaskFailedWithName": "{0} neuspjelo",
"ScheduledTaskStartedWithName": "{0} pokrenuto",
- "ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
- "Shows": "Shows",
+ "ServerNameNeedsToBeRestarted": "{0} treba biti ponovno pokrenuto",
+ "Shows": "Serije",
"Songs": "Pjesme",
"StartupEmbyServerIsLoading": "Jellyfin Server se učitava. Pokušajte ponovo kasnije.",
"SubtitleDownloadFailureForItem": "Titlovi prijevoda nisu preuzeti za {0}",
- "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
+ "SubtitleDownloadFailureFromForItem": "Prijevodi nisu uspješno preuzeti {0} od {1}",
"Sync": "Sink.",
"System": "Sistem",
- "TvShows": "TV Shows",
+ "TvShows": "Serije",
"User": "Korisnik",
"UserCreatedWithName": "Korisnik {0} je stvoren",
"UserDeletedWithName": "Korisnik {0} je obrisan",
@@ -87,10 +87,10 @@
"UserOfflineFromDevice": "{0} se odspojilo od {1}",
"UserOnlineFromDevice": "{0} je online od {1}",
"UserPasswordChangedWithName": "Lozinka je promijenjena za korisnika {0}",
- "UserPolicyUpdatedWithName": "User policy has been updated for {0}",
+ "UserPolicyUpdatedWithName": "Pravila za korisnika su ažurirana za {0}",
"UserStartedPlayingItemWithValues": "{0} je pokrenuo {1}",
"UserStoppedPlayingItemWithValues": "{0} je zaustavio {1}",
- "ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
+ "ValueHasBeenAddedToLibrary": "{0} je dodano u medijsku biblioteku",
"ValueSpecialEpisodeName": "Specijal - {0}",
"VersionNumber": "Verzija {0}",
"TaskRefreshLibraryDescription": "Skenira vašu medijsku knjižnicu sa novim datotekama i osvježuje metapodatke.",
@@ -100,5 +100,19 @@
"TaskCleanCacheDescription": "Briše priručne datoteke nepotrebne za sistem.",
"TaskCleanCache": "Očisti priručnu memoriju",
"TasksApplicationCategory": "Aplikacija",
- "TasksMaintenanceCategory": "Održavanje"
+ "TasksMaintenanceCategory": "Održavanje",
+ "TaskDownloadMissingSubtitlesDescription": "Pretraživanje interneta za prijevodima koji nedostaju bazirano na konfiguraciji meta podataka.",
+ "TaskDownloadMissingSubtitles": "Preuzimanje prijevoda koji nedostaju",
+ "TaskRefreshChannelsDescription": "Osvježava informacije o internet kanalima.",
+ "TaskRefreshChannels": "Osvježi kanale",
+ "TaskCleanTranscodeDescription": "Briše transkodirane fajlove starije od jednog dana.",
+ "TaskCleanTranscode": "Očisti direktorij za transkodiranje",
+ "TaskUpdatePluginsDescription": "Preuzima i instalira ažuriranja za dodatke koji su podešeni da se ažuriraju automatski.",
+ "TaskUpdatePlugins": "Ažuriraj dodatke",
+ "TaskRefreshPeopleDescription": "Ažurira meta podatke za glumce i redatelje u vašoj medijskoj biblioteci.",
+ "TaskRefreshPeople": "Osvježi ljude",
+ "TaskCleanLogsDescription": "Briši logove koji su stariji od {0} dana.",
+ "TaskCleanLogs": "Očisti direktorij sa logovima",
+ "TasksChannelsCategory": "Internet kanali",
+ "TasksLibraryCategory": "Biblioteka"
}
diff --git a/Emby.Server.Implementations/Localization/Core/mr.json b/Emby.Server.Implementations/Localization/Core/mr.json
index 50b6360d8..b6db2b0f2 100644
--- a/Emby.Server.Implementations/Localization/Core/mr.json
+++ b/Emby.Server.Implementations/Localization/Core/mr.json
@@ -57,5 +57,7 @@
"HeaderCameraUploads": "कॅमेरा अपलोड",
"CameraImageUploadedFrom": "एक नवीन कॅमेरा चित्र {0} येथून अपलोड केले आहे",
"Application": "अ‍ॅप्लिकेशन",
- "AppDeviceValues": "अ‍ॅप: {0}, यंत्र: {1}"
+ "AppDeviceValues": "अ‍ॅप: {0}, यंत्र: {1}",
+ "Collections": "संग्रह",
+ "ChapterNameValue": "धडा {0}"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json
index 79d078d4a..7f8df1289 100644
--- a/Emby.Server.Implementations/Localization/Core/ms.json
+++ b/Emby.Server.Implementations/Localization/Core/ms.json
@@ -5,47 +5,47 @@
"Artists": "Artis",
"AuthenticationSucceededWithUserName": "{0} berjaya disahkan",
"Books": "Buku-buku",
- "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
+ "CameraImageUploadedFrom": "Ada gambar dari kamera yang baru dimuat naik melalui {0}",
"Channels": "Saluran",
- "ChapterNameValue": "Chapter {0}",
+ "ChapterNameValue": "Bab {0}",
"Collections": "Koleksi",
- "DeviceOfflineWithName": "{0} has disconnected",
- "DeviceOnlineWithName": "{0} is connected",
+ "DeviceOfflineWithName": "{0} telah diputuskan sambungan",
+ "DeviceOnlineWithName": "{0} telah disambung",
"FailedLoginAttemptWithUserName": "Cubaan log masuk gagal dari {0}",
- "Favorites": "Favorites",
- "Folders": "Folders",
+ "Favorites": "Kegemaran",
+ "Folders": "Fail-fail",
"Genres": "Genre-genre",
- "HeaderAlbumArtists": "Album Artists",
+ "HeaderAlbumArtists": "Album Artis-artis",
"HeaderCameraUploads": "Muatnaik Kamera",
"HeaderContinueWatching": "Terus Menonton",
- "HeaderFavoriteAlbums": "Favorite Albums",
- "HeaderFavoriteArtists": "Favorite Artists",
- "HeaderFavoriteEpisodes": "Favorite Episodes",
- "HeaderFavoriteShows": "Favorite Shows",
- "HeaderFavoriteSongs": "Favorite 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",
+ "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 Personal",
+ "Inherit": "Mewarisi",
+ "ItemAddedWithName": "{0} telah ditambahkan ke dalam pustaka",
+ "ItemRemovedWithName": "{0} telah dibuang daripada pustaka",
"LabelIpAddressValue": "Alamat IP: {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",
+ "LabelRunningTimeValue": "Masa berjalan: {0}",
+ "Latest": "Terbaru",
+ "MessageApplicationUpdated": "Jellyfin Server telah dikemas kini",
+ "MessageApplicationUpdatedTo": "Jellyfin Server telah dikemas kini ke {0}",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Konfigurasi pelayan di bahagian {0} telah dikemas kini",
+ "MessageServerConfigurationUpdated": "Konfigurasi pelayan telah dikemas kini",
+ "MixedContent": "Kandungan campuran",
+ "Movies": "Filem",
"Music": "Muzik",
"MusicVideos": "Video muzik",
- "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",
+ "NameInstallFailed": "{0} pemasangan gagal",
+ "NameSeasonNumber": "Musim {0}",
+ "NameSeasonUnknown": "Musim Tidak Diketahui",
+ "NewVersionIsAvailable": "Versi terbaru Jellyfin Server bersedia untuk dimuat turunkan.",
+ "NotificationOptionApplicationUpdateAvailable": "Kemas kini aplikasi telah sedia",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionAudioPlayback": "Audio playback started",
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
diff --git a/Emby.Server.Implementations/Localization/Core/ne.json b/Emby.Server.Implementations/Localization/Core/ne.json
new file mode 100644
index 000000000..38c073709
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/ne.json
@@ -0,0 +1,86 @@
+{
+ "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": "हेर्न जारी राख्नुहोस्",
+ "HeaderCameraUploads": "क्यामेरा अपलोडहरू",
+ "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": "",
+ "StartupEmbyServerIsLoading": "Jellyfin सर्भर लोड हुँदैछ। कृपया छिट्टै फेरि प्रयास गर्नुहोस्।",
+ "Songs": "गीतहरू",
+ "Shows": "शोहरू",
+ "ServerNameNeedsToBeRestarted": "{0} लाई पुन: सुरु गर्नु पर्छ",
+ "ScheduledTaskStartedWithName": "{0} सुरु भयो",
+ "ScheduledTaskFailedWithName": "{0} असफल",
+ "ProviderValue": "प्रदायक: {0}",
+ "Plugin": "प्लगइनहरू",
+ "Playlists": "प्लेलिस्टहरू",
+ "Photos": "तस्बिरहरु",
+ "NotificationOptionVideoPlaybackStopped": "भिडियो प्लेब्याक रोकियो",
+ "NotificationOptionVideoPlayback": "भिडियो प्लेब्याक सुरु भयो"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json
index 25c5b9053..b534d0bbe 100644
--- a/Emby.Server.Implementations/Localization/Core/pt.json
+++ b/Emby.Server.Implementations/Localization/Core/pt.json
@@ -101,7 +101,17 @@
"TaskCleanLogsDescription": "Deletar arquivos de log que existe a mais de {0} dias.",
"TaskCleanLogs": "Limpar diretório de log",
"TaskRefreshLibrary": "Escanear biblioteca de mídias",
- "TaskRefreshChapterImagesDescription": "Criar miniaturas para videos que tem capítulos.",
- "TaskCleanCacheDescription": "Deletar arquivos de cache que não são mais usados pelo sistema.",
- "TasksChannelsCategory": "Canais de Internet"
+ "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": "Download das 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": "Download e instala as atualizações para plug-ins configurados para atualização automática.",
+ "TaskRefreshPeopleDescription": "Atualiza os metadados para atores e diretores na tua biblioteca de media.",
+ "TaskRefreshPeople": "Atualizar pessoas",
+ "TaskRefreshLibraryDescription": "Pesquisa a tua biblioteca de media por novos ficheiros e atualiza os metadados."
}
diff --git a/Emby.Server.Implementations/Localization/Core/th.json b/Emby.Server.Implementations/Localization/Core/th.json
index 32538ac03..576aaeb1b 100644
--- a/Emby.Server.Implementations/Localization/Core/th.json
+++ b/Emby.Server.Implementations/Localization/Core/th.json
@@ -67,5 +67,7 @@
"Artists": "นักแสดง",
"Application": "แอปพลิเคชั่น",
"AppDeviceValues": "App: {0}, อุปกรณ์: {1}",
- "Albums": "อัลบั้ม"
+ "Albums": "อัลบั้ม",
+ "ScheduledTaskStartedWithName": "{0} เริ่มต้น",
+ "ScheduledTaskFailedWithName": "{0} ล้มเหลว"
}
diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json
index 0804fc927..1ac62baca 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-HK.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json
@@ -1,6 +1,6 @@
{
"Albums": "專輯",
- "AppDeviceValues": "軟件: {0}, 設備: {1}",
+ "AppDeviceValues": "程式: {0}, 設備: {1}",
"Application": "應用程式",
"Artists": "藝人",
"AuthenticationSucceededWithUserName": "{0} 授權成功",
@@ -113,5 +113,6 @@
"TaskCleanCacheDescription": "刪除系統不再需要的緩存文件。",
"TaskCleanCache": "清理緩存目錄",
"TasksChannelsCategory": "互聯網頻道",
- "TasksLibraryCategory": "庫"
+ "TasksLibraryCategory": "庫",
+ "TaskRefreshPeople": "刷新人物"
}
diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs
index f347540c7..0781a0e33 100644
--- a/Emby.Server.Implementations/Net/SocketFactory.cs
+++ b/Emby.Server.Implementations/Net/SocketFactory.cs
@@ -19,6 +19,7 @@ namespace Emby.Server.Implementations.Net
var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
try
{
+ retVal.EnableBroadcast = true;
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
@@ -46,6 +47,7 @@ namespace Emby.Server.Implementations.Net
var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
try
{
+ retVal.EnableBroadcast = true;
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4);
@@ -98,7 +100,6 @@ namespace Emby.Server.Implementations.Net
}
catch (SocketException)
{
-
}
try
@@ -109,12 +110,12 @@ namespace Emby.Server.Implementations.Net
}
catch (SocketException)
{
-
}
try
{
- //retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
+ retVal.EnableBroadcast = true;
+ // retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive);
var localIp = IPAddress.Any;
diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs
index 848f82d85..b51c03446 100644
--- a/Emby.Server.Implementations/Net/UdpSocket.cs
+++ b/Emby.Server.Implementations/Net/UdpSocket.cs
@@ -37,7 +37,10 @@ namespace Emby.Server.Implementations.Net
public UdpSocket(Socket socket, int localPort, IPAddress ip)
{
- if (socket == null) throw new ArgumentNullException(nameof(socket));
+ if (socket == null)
+ {
+ throw new ArgumentNullException(nameof(socket));
+ }
_socket = socket;
_localPort = localPort;
@@ -103,7 +106,10 @@ namespace Emby.Server.Implementations.Net
public UdpSocket(Socket socket, IPEndPoint endPoint)
{
- if (socket == null) throw new ArgumentNullException(nameof(socket));
+ if (socket == null)
+ {
+ throw new ArgumentNullException(nameof(socket));
+ }
_socket = socket;
_socket.Connect(endPoint);
diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs
index 45864bb42..50cb44b28 100644
--- a/Emby.Server.Implementations/Networking/NetworkManager.cs
+++ b/Emby.Server.Implementations/Networking/NetworkManager.cs
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
@@ -13,6 +12,9 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Networking
{
+ /// <summary>
+ /// Class to take care of network interface management.
+ /// </summary>
public class NetworkManager : INetworkManager
{
private readonly ILogger<NetworkManager> _logger;
@@ -21,8 +23,14 @@ namespace Emby.Server.Implementations.Networking
private readonly object _localIpAddressSyncLock = new object();
private readonly object _subnetLookupLock = new object();
- private Dictionary<string, List<string>> _subnetLookup = new Dictionary<string, List<string>>(StringComparer.Ordinal);
+ private readonly Dictionary<string, List<string>> _subnetLookup = new Dictionary<string, List<string>>(StringComparer.Ordinal);
+ private List<PhysicalAddress> _macAddresses;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="NetworkManager"/> class.
+ /// </summary>
+ /// <param name="logger">Logger to use for messages.</param>
public NetworkManager(ILogger<NetworkManager> logger)
{
_logger = logger;
@@ -31,8 +39,10 @@ namespace Emby.Server.Implementations.Networking
NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
}
+ /// <inheritdoc/>
public event EventHandler NetworkChanged;
+ /// <inheritdoc/>
public Func<string[]> LocalSubnetsFn { get; set; }
private void OnNetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
@@ -58,13 +68,14 @@ namespace Emby.Server.Implementations.Networking
NetworkChanged?.Invoke(this, EventArgs.Empty);
}
- public IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface = true)
+ /// <inheritdoc/>
+ public IPAddress[] GetLocalIpAddresses()
{
lock (_localIpAddressSyncLock)
{
if (_localIpAddresses == null)
{
- var addresses = GetLocalIpAddressesInternal(ignoreVirtualInterface).ToArray();
+ var addresses = GetLocalIpAddressesInternal().ToArray();
_localIpAddresses = addresses;
}
@@ -73,42 +84,47 @@ namespace Emby.Server.Implementations.Networking
}
}
- private List<IPAddress> GetLocalIpAddressesInternal(bool ignoreVirtualInterface)
+ private List<IPAddress> GetLocalIpAddressesInternal()
{
- var list = GetIPsDefault(ignoreVirtualInterface).ToList();
+ var list = GetIPsDefault().ToList();
if (list.Count == 0)
{
list = GetLocalIpAddressesFallback().GetAwaiter().GetResult().ToList();
}
- var listClone = list.ToList();
+ var listClone = new List<IPAddress>();
- return list
- .OrderBy(i => i.AddressFamily == AddressFamily.InterNetwork ? 0 : 1)
- .ThenBy(i => listClone.IndexOf(i))
- .Where(FilterIpAddress)
- .GroupBy(i => i.ToString())
- .Select(x => x.First())
- .ToList();
- }
+ var subnets = LocalSubnetsFn();
- private static bool FilterIpAddress(IPAddress address)
- {
- if (address.IsIPv6LinkLocal
- || address.ToString().StartsWith("169.", StringComparison.OrdinalIgnoreCase))
+ foreach (var i in list)
{
- return false;
+ if (i.IsIPv6LinkLocal || i.ToString().StartsWith("169.254.", StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ if (Array.IndexOf(subnets, $"[{i}]") == -1)
+ {
+ listClone.Add(i);
+ }
}
- return true;
+ return listClone
+ .OrderBy(i => i.AddressFamily == AddressFamily.InterNetwork ? 0 : 1)
+ // .ThenBy(i => listClone.IndexOf(i))
+ .GroupBy(i => i.ToString())
+ .Select(x => x.First())
+ .ToList();
}
+ /// <inheritdoc/>
public bool IsInPrivateAddressSpace(string endpoint)
{
return IsInPrivateAddressSpace(endpoint, true);
}
+ // Checks if the address in endpoint is an RFC1918, RFC1122, or RFC3927 address
private bool IsInPrivateAddressSpace(string endpoint, bool checkSubnets)
{
if (string.Equals(endpoint, "::1", StringComparison.OrdinalIgnoreCase))
@@ -116,12 +132,12 @@ namespace Emby.Server.Implementations.Networking
return true;
}
- // ipv6
+ // IPV6
if (endpoint.Split('.').Length > 4)
{
// Handle ipv4 mapped to ipv6
var originalEndpoint = endpoint;
- endpoint = endpoint.Replace("::ffff:", string.Empty);
+ endpoint = endpoint.Replace("::ffff:", string.Empty, StringComparison.OrdinalIgnoreCase);
if (string.Equals(endpoint, originalEndpoint, StringComparison.OrdinalIgnoreCase))
{
@@ -130,23 +146,26 @@ namespace Emby.Server.Implementations.Networking
}
// Private address space:
- // http://en.wikipedia.org/wiki/Private_network
- if (endpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(endpoint, "localhost", StringComparison.OrdinalIgnoreCase))
{
- return Is172AddressPrivate(endpoint);
+ return true;
}
- if (endpoint.StartsWith("localhost", StringComparison.OrdinalIgnoreCase) ||
- endpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) ||
- endpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase))
+ if (!IPAddress.TryParse(endpoint, out var ipAddress))
{
- return true;
+ return false;
}
- if (checkSubnets && endpoint.StartsWith("192.168", StringComparison.OrdinalIgnoreCase))
+ byte[] octet = ipAddress.GetAddressBytes();
+
+ if ((octet[0] == 10) ||
+ (octet[0] == 172 && (octet[1] >= 16 && octet[1] <= 31)) || // RFC1918
+ (octet[0] == 192 && octet[1] == 168) || // RFC1918
+ (octet[0] == 127) || // RFC1122
+ (octet[0] == 169 && octet[1] == 254)) // RFC3927
{
- return true;
+ return false;
}
if (checkSubnets && IsInPrivateAddressSpaceAndLocalSubnet(endpoint))
@@ -157,6 +176,7 @@ namespace Emby.Server.Implementations.Networking
return false;
}
+ /// <inheritdoc/>
public bool IsInPrivateAddressSpaceAndLocalSubnet(string endpoint)
{
if (endpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase))
@@ -167,7 +187,7 @@ namespace Emby.Server.Implementations.Networking
foreach (var subnet_Match in subnets)
{
- //logger.LogDebug("subnet_Match:" + subnet_Match);
+ // logger.LogDebug("subnet_Match:" + subnet_Match);
if (endpoint.StartsWith(subnet_Match + ".", StringComparison.OrdinalIgnoreCase))
{
@@ -179,6 +199,7 @@ namespace Emby.Server.Implementations.Networking
return false;
}
+ // Gives a list of possible subnets from the system whose interface ip starts with endpointFirstPart
private List<string> GetSubnets(string endpointFirstPart)
{
lock (_subnetLookupLock)
@@ -224,46 +245,81 @@ namespace Emby.Server.Implementations.Networking
}
}
- private static bool Is172AddressPrivate(string endpoint)
- {
- for (var i = 16; i <= 31; i++)
- {
- if (endpoint.StartsWith("172." + i.ToString(CultureInfo.InvariantCulture) + ".", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- }
-
- return false;
- }
-
+ /// <inheritdoc/>
public bool IsInLocalNetwork(string endpoint)
{
return IsInLocalNetworkInternal(endpoint, true);
}
+ /// <inheritdoc/>
public bool IsAddressInSubnets(string addressString, string[] subnets)
{
return IsAddressInSubnets(IPAddress.Parse(addressString), addressString, subnets);
}
+ /// <inheritdoc/>
+ public bool IsAddressInSubnets(IPAddress address, bool excludeInterfaces, bool excludeRFC)
+ {
+ byte[] octet = address.GetAddressBytes();
+
+ if ((octet[0] == 127) || // RFC1122
+ (octet[0] == 169 && octet[1] == 254)) // RFC3927
+ {
+ // don't use on loopback or 169 interfaces
+ return false;
+ }
+
+ string addressString = address.ToString();
+ string excludeAddress = "[" + addressString + "]";
+ var subnets = LocalSubnetsFn();
+
+ // Include any address if LAN subnets aren't specified
+ if (subnets.Length == 0)
+ {
+ return true;
+ }
+
+ // Exclude any addresses if they appear in the LAN list in [ ]
+ if (Array.IndexOf(subnets, excludeAddress) != -1)
+ {
+ return false;
+ }
+
+ return IsAddressInSubnets(address, addressString, subnets);
+ }
+
+ /// <summary>
+ /// Checks if the give address falls within the ranges given in [subnets]. The addresses in subnets can be hosts or subnets in the CIDR format.
+ /// </summary>
+ /// <param name="address">IPAddress version of the address.</param>
+ /// <param name="addressString">The address to check.</param>
+ /// <param name="subnets">If true, check against addresses in the LAN settings which have [] arroud and return true if it matches the address give in address.</param>
+ /// <returns><c>false</c>if the address isn't in the subnets, <c>true</c> otherwise.</returns>
private static bool IsAddressInSubnets(IPAddress address, string addressString, string[] subnets)
{
foreach (var subnet in subnets)
{
var normalizedSubnet = subnet.Trim();
-
+ // Is the subnet a host address and does it match the address being passes?
if (string.Equals(normalizedSubnet, addressString, StringComparison.OrdinalIgnoreCase))
{
return true;
}
+ // Parse CIDR subnets and see if address falls within it.
if (normalizedSubnet.Contains('/', StringComparison.Ordinal))
{
- var ipNetwork = IPNetwork.Parse(normalizedSubnet);
- if (ipNetwork.Contains(address))
+ try
{
- return true;
+ var ipNetwork = IPNetwork.Parse(normalizedSubnet);
+ if (ipNetwork.Contains(address))
+ {
+ return true;
+ }
+ }
+ catch
+ {
+ // Ignoring - invalid subnet passed encountered.
}
}
}
@@ -288,7 +344,7 @@ namespace Emby.Server.Implementations.Networking
var localSubnets = localSubnetsFn();
foreach (var subnet in localSubnets)
{
- // only validate if there's at least one valid entry
+ // Only validate if there's at least one valid entry.
if (!string.IsNullOrWhiteSpace(subnet))
{
return IsAddressInSubnets(address, addressString, localSubnets) || IsInPrivateAddressSpace(addressString, false);
@@ -345,7 +401,7 @@ namespace Emby.Server.Implementations.Networking
}
catch (InvalidOperationException)
{
- // Can happen with reverse proxy or IIS url rewriting
+ // Can happen with reverse proxy or IIS url rewriting?
}
catch (Exception ex)
{
@@ -362,7 +418,7 @@ namespace Emby.Server.Implementations.Networking
return Dns.GetHostAddressesAsync(hostName);
}
- private IEnumerable<IPAddress> GetIPsDefault(bool ignoreVirtualInterface)
+ private IEnumerable<IPAddress> GetIPsDefault()
{
IEnumerable<NetworkInterface> interfaces;
@@ -382,15 +438,7 @@ namespace Emby.Server.Implementations.Networking
{
var ipProperties = network.GetIPProperties();
- // Try to exclude virtual adapters
- // http://stackoverflow.com/questions/8089685/c-sharp-finding-my-machines-local-ip-address-and-not-the-vms
- var addr = ipProperties.GatewayAddresses.FirstOrDefault();
- if (addr == null
- || (ignoreVirtualInterface
- && (addr.Address.Equals(IPAddress.Any) || addr.Address.Equals(IPAddress.IPv6Any))))
- {
- return Enumerable.Empty<IPAddress>();
- }
+ // Exclude any addresses if they appear in the LAN list in [ ]
return ipProperties.UnicastAddresses
.Select(i => i.Address)
@@ -411,7 +459,7 @@ namespace Emby.Server.Implementations.Networking
}
/// <summary>
- /// Gets a random port number that is currently available
+ /// Gets a random port number that is currently available.
/// </summary>
/// <returns>System.Int32.</returns>
public int GetRandomUnusedTcpPort()
@@ -423,33 +471,29 @@ namespace Emby.Server.Implementations.Networking
return port;
}
+ /// <inheritdoc/>
public int GetRandomUnusedUdpPort()
{
var localEndPoint = new IPEndPoint(IPAddress.Any, 0);
using (var udpClient = new UdpClient(localEndPoint))
{
- var port = ((IPEndPoint)udpClient.Client.LocalEndPoint).Port;
- return port;
+ return ((IPEndPoint)udpClient.Client.LocalEndPoint).Port;
}
}
- private List<PhysicalAddress> _macAddresses;
+ /// <inheritdoc/>
public List<PhysicalAddress> GetMacAddresses()
{
- if (_macAddresses == null)
- {
- _macAddresses = GetMacAddressesInternal().ToList();
- }
-
- return _macAddresses;
+ return _macAddresses ??= GetMacAddressesInternal().ToList();
}
private static IEnumerable<PhysicalAddress> GetMacAddressesInternal()
=> NetworkInterface.GetAllNetworkInterfaces()
.Where(i => i.NetworkInterfaceType != NetworkInterfaceType.Loopback)
.Select(x => x.GetPhysicalAddress())
- .Where(x => x != null && x != PhysicalAddress.None);
+ .Where(x => !x.Equals(PhysicalAddress.None));
+ /// <inheritdoc/>
public bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask)
{
IPAddress network1 = GetNetworkAddress(address1, subnetMask);
@@ -476,6 +520,7 @@ namespace Emby.Server.Implementations.Networking
return new IPAddress(broadcastAddress);
}
+ /// <inheritdoc/>
public IPAddress GetLocalIpSubnetMask(IPAddress address)
{
NetworkInterface[] interfaces;
@@ -496,14 +541,11 @@ namespace Emby.Server.Implementations.Networking
foreach (NetworkInterface ni in interfaces)
{
- if (ni.GetIPProperties().GatewayAddresses.FirstOrDefault() != null)
+ foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses)
{
- foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses)
+ if (ip.Address.Equals(address) && ip.IPv4Mask != null)
{
- if (ip.Address.Equals(address) && ip.IPv4Mask != null)
- {
- return ip.IPv4Mask;
- }
+ return ip.IPv4Mask;
}
}
}
diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs
index 9feef5d8d..5dd1af4b8 100644
--- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs
+++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs
@@ -401,6 +401,7 @@ namespace Emby.Server.Implementations.Playlists
{
entry.Duration = TimeSpan.FromTicks(child.RunTimeTicks.Value);
}
+
playlist.PlaylistEntries.Add(entry);
}
@@ -466,7 +467,7 @@ namespace Emby.Server.Implementations.Playlists
playlist.PlaylistEntries.Add(entry);
}
- string text = new M3u8Content().ToText(playlist);
+ string text = new M3uContent().ToText(playlist);
File.WriteAllText(playlistPath, text);
}
@@ -538,13 +539,21 @@ namespace Emby.Server.Implementations.Playlists
private static string UnEscape(string content)
{
- if (content == null) return content;
+ if (content == null)
+ {
+ return content;
+ }
+
return content.Replace("&amp;", "&").Replace("&apos;", "'").Replace("&quot;", "\"").Replace("&gt;", ">").Replace("&lt;", "<");
}
private static string Escape(string content)
{
- if (content == null) return null;
+ if (content == null)
+ {
+ return null;
+ }
+
return content.Replace("&", "&amp;").Replace("'", "&apos;").Replace("\"", "&quot;").Replace(">", "&gt;").Replace("<", "&lt;");
}
diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
index e58c335a8..8a900f42c 100644
--- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
@@ -18,7 +18,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.ScheduledTasks
{
/// <summary>
- /// Class ScheduledTaskWorker
+ /// Class ScheduledTaskWorker.
/// </summary>
public class ScheduledTaskWorker : IScheduledTaskWorker
{
@@ -111,11 +111,11 @@ namespace Emby.Server.Implementations.ScheduledTasks
private bool _readFromFile = false;
/// <summary>
- /// The _last execution result
+ /// The _last execution result.
/// </summary>
private TaskResult _lastExecutionResult;
/// <summary>
- /// The _last execution result sync lock
+ /// The _last execution result sync lock.
/// </summary>
private readonly object _lastExecutionResultSyncLock = new object();
/// <summary>
@@ -143,12 +143,14 @@ namespace Emby.Server.Implementations.ScheduledTasks
Logger.LogError(ex, "Error deserializing {File}", path);
}
}
+
_readFromFile = true;
}
}
return _lastExecutionResult;
}
+
private set
{
_lastExecutionResult = value;
@@ -182,7 +184,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
public string Category => ScheduledTask.Category;
/// <summary>
- /// Gets the current cancellation token
+ /// Gets the current cancellation token.
/// </summary>
/// <value>The current cancellation token source.</value>
private CancellationTokenSource CurrentCancellationTokenSource { get; set; }
@@ -261,6 +263,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
var triggers = InternalTriggers;
return triggers.Select(i => i.Item1).ToArray();
}
+
set
{
if (value == null)
@@ -278,7 +281,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
- /// The _id
+ /// The _id.
/// </summary>
private string _id;
@@ -358,7 +361,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
private Task _currentTask;
/// <summary>
- /// Executes the task
+ /// Executes the task.
/// </summary>
/// <param name="options">Task options.</param>
/// <returns>Task.</returns>
@@ -453,7 +456,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
- /// Stops the task if it is currently executing
+ /// 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()
@@ -640,6 +643,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
Logger.LogError(ex, "Error calling CancellationToken.Cancel();");
}
}
+
var task = _currentTask;
if (task != null)
{
@@ -675,6 +679,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
Logger.LogError(ex, "Error calling CancellationToken.Dispose();");
}
}
+
if (wassRunning)
{
OnTaskCompleted(startTime, DateTime.UtcNow, TaskCompletionStatus.Aborted, null);
@@ -683,7 +688,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
- /// Converts a TaskTriggerInfo into a concrete BaseTaskTrigger
+ /// Converts a TaskTriggerInfo into a concrete BaseTaskTrigger.
/// </summary>
/// <param name="info">The info.</param>
/// <returns>BaseTaskTrigger.</returns>
@@ -753,7 +758,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
- /// Disposes each trigger
+ /// Disposes each trigger.
/// </summary>
private void DisposeTriggers()
{
diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
index 94220ac5d..3fe15ec68 100644
--- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
@@ -15,7 +15,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.ScheduledTasks
{
/// <summary>
- /// Class TaskManager
+ /// Class TaskManager.
/// </summary>
public class TaskManager : ITaskManager
{
@@ -23,13 +23,13 @@ namespace Emby.Server.Implementations.ScheduledTasks
public event EventHandler<TaskCompletionEventArgs> TaskCompleted;
/// <summary>
- /// Gets the list of Scheduled Tasks
+ /// Gets the list of Scheduled Tasks.
/// </summary>
/// <value>The scheduled tasks.</value>
public IScheduledTaskWorker[] ScheduledTasks { get; private set; }
/// <summary>
- /// The _task queue
+ /// The _task queue.
/// </summary>
private readonly ConcurrentQueue<Tuple<Type, TaskOptions>> _taskQueue =
new ConcurrentQueue<Tuple<Type, TaskOptions>>();
@@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
- /// Cancels if running
+ /// Cancels if running.
/// </summary>
/// <typeparam name="T"></typeparam>
public void CancelIfRunning<T>()
@@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// Queues the scheduled task.
/// </summary>
/// <typeparam name="T"></typeparam>
- /// <param name="options">Task options</param>
+ /// <param name="options">Task options.</param>
public void QueueScheduledTask<T>(TaskOptions options)
where T : IScheduledTask
{
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
index 9028222dd..3854be703 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
@@ -163,7 +163,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
catch (ObjectDisposedException)
{
- //TODO Investigate and properly fix.
+ // TODO Investigate and properly fix.
break;
}
}
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
index 966b549b2..e29fcfb5f 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
@@ -13,7 +13,7 @@ using MediaBrowser.Model.Globalization;
namespace Emby.Server.Implementations.ScheduledTasks.Tasks
{
/// <summary>
- /// Deletes old cache files
+ /// Deletes old cache files.
/// </summary>
public class DeleteCacheFileTask : IScheduledTask, IConfigurableScheduledTask
{
@@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
}
/// <summary>
- /// Creates the triggers that define when the task will run
+ /// Creates the triggers that define when the task will run.
/// </summary>
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
@@ -57,7 +57,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
}
/// <summary>
- /// Returns the task to be executed
+ /// Returns the task to be executed.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress.</param>
@@ -93,7 +93,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
/// <summary>
- /// Deletes the cache files from directory with a last write time less than a given date
+ /// Deletes the cache files from directory with a last write time less than a given date.
/// </summary>
/// <param name="cancellationToken">The task cancellation token.</param>
/// <param name="directory">The directory.</param>
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs
index 53cf9a0a5..691408167 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs
@@ -13,7 +13,7 @@ using MediaBrowser.Model.Globalization;
namespace Emby.Server.Implementations.ScheduledTasks.Tasks
{
/// <summary>
- /// Deletes all transcoding temp files
+ /// Deletes all transcoding temp files.
/// </summary>
public class DeleteTranscodeFileTask : IScheduledTask, IConfigurableScheduledTask
{
diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs
index c7819d4c0..eb628ec5f 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs
@@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
private Timer Timer { get; set; }
/// <summary>
- /// Stars waiting for the trigger action
+ /// Stars waiting for the trigger action.
/// </summary>
/// <param name="lastResult">The last result.</param>
/// <param name="logger">The logger.</param>
@@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
- /// Stops waiting for the trigger action
+ /// Stops waiting for the trigger action.
/// </summary>
public void Stop()
{
diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs
index 74cd4ef1e..247a6785a 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs
@@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.ScheduledTasks
{
/// <summary>
- /// Represents a task trigger that runs repeatedly on an interval
+ /// Represents a task trigger that runs repeatedly on an interval.
/// </summary>
public class IntervalTrigger : ITaskTrigger
{
@@ -31,7 +31,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
private DateTime _lastStartDate;
/// <summary>
- /// Stars waiting for the trigger action
+ /// Stars waiting for the trigger action.
/// </summary>
/// <param name="lastResult">The last result.</param>
/// <param name="logger">The logger.</param>
@@ -70,7 +70,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
- /// Stops waiting for the trigger action
+ /// Stops waiting for the trigger action.
/// </summary>
public void Stop()
{
diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs
index e171a9e9f..96e5d8897 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs
@@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
- /// Stars waiting for the trigger action
+ /// Stars waiting for the trigger action.
/// </summary>
/// <param name="lastResult">The last result.</param>
/// <param name="logger">The logger.</param>
@@ -42,7 +42,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
- /// Stops waiting for the trigger action
+ /// Stops waiting for the trigger action.
/// </summary>
public void Stop()
{
diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs
index ad0b57af6..4f1bf5c19 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs
@@ -6,12 +6,12 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.ScheduledTasks
{
/// <summary>
- /// Represents a task trigger that fires on a weekly basis
+ /// Represents a task trigger that fires on a weekly basis.
/// </summary>
public class WeeklyTrigger : ITaskTrigger
{
/// <summary>
- /// Get the time of day to trigger the task to run
+ /// Get the time of day to trigger the task to run.
/// </summary>
/// <value>The time of day.</value>
public TimeSpan TimeOfDay { get; set; }
@@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
private Timer Timer { get; set; }
/// <summary>
- /// Stars waiting for the trigger action
+ /// Stars waiting for the trigger action.
/// </summary>
/// <param name="lastResult">The last result.</param>
/// <param name="logger">The logger.</param>
@@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
- /// Stops waiting for the trigger action
+ /// Stops waiting for the trigger action.
/// </summary>
public void Stop()
{
diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs
index 750890ec8..4dfadc703 100644
--- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs
+++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs
@@ -61,7 +61,6 @@ namespace Emby.Server.Implementations.Security
AddColumn(db, "AccessTokens", "UserName", "TEXT", existingColumnNames);
AddColumn(db, "AccessTokens", "DateLastActivity", "DATETIME", existingColumnNames);
AddColumn(db, "AccessTokens", "AppVersion", "TEXT", existingColumnNames);
-
}, TransactionMode);
connection.RunQueries(new[]
@@ -99,7 +98,7 @@ namespace Emby.Server.Implementations.Security
statement.TryBind("@AppName", info.AppName);
statement.TryBind("@AppVersion", info.AppVersion);
statement.TryBind("@DeviceName", info.DeviceName);
- statement.TryBind("@UserId", (info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N", CultureInfo.InvariantCulture)));
+ statement.TryBind("@UserId", info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N", CultureInfo.InvariantCulture));
statement.TryBind("@UserName", info.UserName);
statement.TryBind("@IsActive", true);
statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue());
@@ -107,7 +106,6 @@ namespace Emby.Server.Implementations.Security
statement.MoveNext();
}
-
}, TransactionMode);
}
}
@@ -133,7 +131,7 @@ namespace Emby.Server.Implementations.Security
statement.TryBind("@AppName", info.AppName);
statement.TryBind("@AppVersion", info.AppVersion);
statement.TryBind("@DeviceName", info.DeviceName);
- statement.TryBind("@UserId", (info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N", CultureInfo.InvariantCulture)));
+ statement.TryBind("@UserId", info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N", CultureInfo.InvariantCulture));
statement.TryBind("@UserName", info.UserName);
statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue());
statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue());
@@ -367,7 +365,6 @@ namespace Emby.Server.Implementations.Security
return result;
}
-
}, ReadTransactionMode);
}
}
@@ -398,7 +395,6 @@ namespace Emby.Server.Implementations.Security
statement.MoveNext();
}
-
}, TransactionMode);
}
}
diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs
index f2b1d06f3..a329b531d 100644
--- a/Emby.Server.Implementations/Services/ResponseHelper.cs
+++ b/Emby.Server.Implementations/Services/ResponseHelper.cs
@@ -40,7 +40,9 @@ namespace Emby.Server.Implementations.Services
if (httpResult != null)
{
if (httpResult.RequestContext == null)
+ {
httpResult.RequestContext = request;
+ }
response.StatusCode = httpResult.Status;
}
@@ -59,8 +61,8 @@ namespace Emby.Server.Implementations.Services
}
}
- //ContentType='text/html' is the default for a HttpResponse
- //Do not override if another has been set
+ // ContentType='text/html' is the default for a HttpResponse
+ // Do not override if another has been set
if (response.ContentType == null || response.ContentType == "text/html")
{
response.ContentType = defaultContentType;
diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs
index e688278b5..857df591a 100644
--- a/Emby.Server.Implementations/Services/ServiceController.cs
+++ b/Emby.Server.Implementations/Services/ServiceController.cs
@@ -59,8 +59,8 @@ namespace Emby.Server.Implementations.Services
ServiceExecGeneral.CreateServiceRunnersFor(requestType, actions);
- //var returnMarker = GetTypeWithGenericTypeDefinitionOf(requestType, typeof(IReturn<>));
- //var responseType = returnMarker != null ?
+ // var returnMarker = GetTypeWithGenericTypeDefinitionOf(requestType, typeof(IReturn<>));
+ // var responseType = returnMarker != null ?
// GetGenericArguments(returnMarker)[0]
// : mi.ReturnType != typeof(object) && mi.ReturnType != typeof(void) ?
// mi.ReturnType
@@ -144,7 +144,10 @@ namespace Emby.Server.Implementations.Services
var yieldedWildcardMatches = RestPath.GetFirstMatchWildCardHashKeys(matchUsingPathParts);
foreach (var potentialHashMatch in yieldedWildcardMatches)
{
- if (!this.RestPathMap.TryGetValue(potentialHashMatch, out firstMatches)) continue;
+ if (!this.RestPathMap.TryGetValue(potentialHashMatch, out firstMatches))
+ {
+ continue;
+ }
var bestScore = -1;
RestPath bestMatch = null;
@@ -182,7 +185,7 @@ namespace Emby.Server.Implementations.Services
serviceRequiresContext.Request = req;
}
- //Executes the service and returns the result
+ // Executes the service and returns the result
return ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetMethodName());
}
}
diff --git a/Emby.Server.Implementations/Services/ServiceExec.cs b/Emby.Server.Implementations/Services/ServiceExec.cs
index 606f2a240..cbc4b754d 100644
--- a/Emby.Server.Implementations/Services/ServiceExec.cs
+++ b/Emby.Server.Implementations/Services/ServiceExec.cs
@@ -42,11 +42,15 @@ namespace Emby.Server.Implementations.Services
}
if (mi.GetParameters().Length != 1)
+ {
continue;
+ }
var actionName = mi.Name;
if (!AllVerbs.Contains(actionName, StringComparer.OrdinalIgnoreCase))
+ {
continue;
+ }
list.Add(mi);
}
@@ -63,7 +67,10 @@ namespace Emby.Server.Implementations.Services
{
foreach (var actionCtx in actions)
{
- if (execMap.ContainsKey(actionCtx.Id)) continue;
+ if (execMap.ContainsKey(actionCtx.Id))
+ {
+ continue;
+ }
execMap[actionCtx.Id] = actionCtx;
}
diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs
index 7f44357e1..a42f88ea0 100644
--- a/Emby.Server.Implementations/Services/ServiceHandler.cs
+++ b/Emby.Server.Implementations/Services/ServiceHandler.cs
@@ -180,7 +180,7 @@ namespace Emby.Server.Implementations.Services
=> string.Equals(method, expected, StringComparison.OrdinalIgnoreCase);
/// <summary>
- /// Duplicate params have their values joined together in a comma-delimited string
+ /// Duplicate params have their values joined together in a comma-delimited string.
/// </summary>
private static Dictionary<string, string> GetFlattenedRequestParams(HttpRequest request)
{
diff --git a/Emby.Server.Implementations/Services/ServiceMethod.cs b/Emby.Server.Implementations/Services/ServiceMethod.cs
index 59ee5908f..5116cc04f 100644
--- a/Emby.Server.Implementations/Services/ServiceMethod.cs
+++ b/Emby.Server.Implementations/Services/ServiceMethod.cs
@@ -9,6 +9,7 @@ namespace Emby.Server.Implementations.Services
public string Id { get; set; }
public ActionInvokerFn ServiceAction { get; set; }
+
public MediaBrowser.Model.Services.IHasRequestFilter[] RequestFilters { get; set; }
public static string Key(Type serviceType, string method, string requestDtoName)
diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs
index 278379a92..89538ae72 100644
--- a/Emby.Server.Implementations/Services/ServicePath.cs
+++ b/Emby.Server.Implementations/Services/ServicePath.cs
@@ -62,7 +62,9 @@ namespace Emby.Server.Implementations.Services
public string Path => this.restPath;
public string Summary { get; private set; }
+
public string Description { get; private set; }
+
public bool IsHidden { get; private set; }
public static string[] GetPathPartsForMatching(string pathInfo)
@@ -118,11 +120,14 @@ namespace Emby.Server.Implementations.Services
var componentsList = new List<string>();
- //We only split on '.' if the restPath has them. Allows for /{action}.{type}
+ // We only split on '.' if the restPath has them. Allows for /{action}.{type}
var hasSeparators = new List<bool>();
foreach (var component in this.restPath.Split(PathSeperatorChar))
{
- if (string.IsNullOrEmpty(component)) continue;
+ if (string.IsNullOrEmpty(component))
+ {
+ continue;
+ }
if (component.IndexOf(VariablePrefix, StringComparison.OrdinalIgnoreCase) != -1
&& component.IndexOf(ComponentSeperator) != -1)
@@ -159,6 +164,7 @@ namespace Emby.Server.Implementations.Services
this.isWildcard[i] = true;
variableName = variableName.Substring(0, variableName.Length - 1);
}
+
this.variablesNames[i] = variableName;
this.VariableArgsCount++;
}
@@ -298,12 +304,12 @@ namespace Emby.Server.Implementations.Services
return -1;
}
- //Routes with least wildcard matches get the highest score
- var score = Math.Max((100 - wildcardMatchCount), 1) * 1000
- //Routes with less variable (and more literal) matches
- + Math.Max((10 - VariableArgsCount), 1) * 100;
+ // Routes with least wildcard matches get the highest score
+ var score = Math.Max(100 - wildcardMatchCount, 1) * 1000
+ // Routes with less variable (and more literal) matches
+ + Math.Max(10 - VariableArgsCount, 1) * 100;
- //Exact verb match is better than ANY
+ // Exact verb match is better than ANY
if (Verbs.Length == 1 && string.Equals(httpMethod, Verbs[0], StringComparison.OrdinalIgnoreCase))
{
score += 10;
@@ -439,12 +445,14 @@ namespace Emby.Server.Implementations.Services
&& requestComponents.Length >= this.TotalComponentsCount - this.wildcardCount;
if (!isValidWildCardPath)
+ {
throw new ArgumentException(
string.Format(
CultureInfo.InvariantCulture,
"Path Mismatch: Request Path '{0}' has invalid number of components compared to: '{1}'",
pathInfo,
this.restPath));
+ }
}
var requestKeyValuesMap = new Dictionary<string, string>();
@@ -470,7 +478,7 @@ namespace Emby.Server.Implementations.Services
+ variableName + " on " + RequestType.GetMethodName());
}
- var value = requestComponents.Length > pathIx ? requestComponents[pathIx] : null; //wildcard has arg mismatch
+ var value = requestComponents.Length > pathIx ? requestComponents[pathIx] : null; // wildcard has arg mismatch
if (value != null && this.isWildcard[i])
{
if (i == this.TotalComponentsCount - 1)
@@ -519,8 +527,8 @@ namespace Emby.Server.Implementations.Services
if (queryStringAndFormData != null)
{
- //Query String and form data can override variable path matches
- //path variables < query string < form data
+ // Query String and form data can override variable path matches
+ // path variables < query string < form data
foreach (var name in queryStringAndFormData)
{
requestKeyValuesMap[name.Key] = name.Value;
diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
index ab22fe019..165bb0fc4 100644
--- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
+++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
@@ -22,7 +22,9 @@ namespace Emby.Server.Implementations.Services
}
public Action<object, object> PropertySetFn { get; private set; }
+
public Func<string, object> PropertyParseStringFn { get; private set; }
+
public Type PropertyType { get; private set; }
}
@@ -83,7 +85,7 @@ namespace Emby.Server.Implementations.Services
if (propertySerializerEntry.PropertyType == typeof(bool))
{
- //InputExtensions.cs#530 MVC Checkbox helper emits extra hidden input field, generating 2 values, first is the real value
+ // InputExtensions.cs#530 MVC Checkbox helper emits extra hidden input field, generating 2 values, first is the real value
propertyTextValue = StringExtensions.LeftPart(propertyTextValue, ',').ToString();
}
diff --git a/Emby.Server.Implementations/Services/SwaggerService.cs b/Emby.Server.Implementations/Services/SwaggerService.cs
index 16142a70d..4f011a678 100644
--- a/Emby.Server.Implementations/Services/SwaggerService.cs
+++ b/Emby.Server.Implementations/Services/SwaggerService.cs
@@ -18,13 +18,21 @@ namespace Emby.Server.Implementations.Services
public class SwaggerSpec
{
public string swagger { get; set; }
+
public string[] schemes { get; set; }
+
public SwaggerInfo info { get; set; }
+
public string host { get; set; }
+
public string basePath { get; set; }
+
public SwaggerTag[] tags { get; set; }
+
public IDictionary<string, Dictionary<string, SwaggerMethod>> paths { get; set; }
+
public Dictionary<string, SwaggerDefinition> definitions { get; set; }
+
public SwaggerComponents components { get; set; }
}
@@ -36,15 +44,20 @@ namespace Emby.Server.Implementations.Services
public class SwaggerSecurityScheme
{
public string name { get; set; }
+
public string type { get; set; }
+
public string @in { get; set; }
}
public class SwaggerInfo
{
public string description { get; set; }
+
public string version { get; set; }
+
public string title { get; set; }
+
public string termsOfService { get; set; }
public SwaggerConcactInfo contact { get; set; }
@@ -53,36 +66,52 @@ namespace Emby.Server.Implementations.Services
public class SwaggerConcactInfo
{
public string email { get; set; }
+
public string name { get; set; }
+
public string url { get; set; }
}
public class SwaggerTag
{
public string description { get; set; }
+
public string name { get; set; }
}
public class SwaggerMethod
{
public string summary { get; set; }
+
public string description { get; set; }
+
public string[] tags { get; set; }
+
public string operationId { get; set; }
+
public string[] consumes { get; set; }
+
public string[] produces { get; set; }
+
public SwaggerParam[] parameters { get; set; }
+
public Dictionary<string, SwaggerResponse> responses { get; set; }
+
public Dictionary<string, string[]>[] security { get; set; }
}
public class SwaggerParam
{
public string @in { get; set; }
+
public string name { get; set; }
+
public string description { get; set; }
+
public bool required { get; set; }
+
public string type { get; set; }
+
public string collectionFormat { get; set; }
}
@@ -97,15 +126,20 @@ namespace Emby.Server.Implementations.Services
public class SwaggerDefinition
{
public string type { get; set; }
+
public Dictionary<string, SwaggerProperty> properties { get; set; }
}
public class SwaggerProperty
{
public string type { get; set; }
+
public string format { get; set; }
+
public string description { get; set; }
+
public string[] @enum { get; set; }
+
public string @default { get; set; }
}
diff --git a/Emby.Server.Implementations/Services/UrlExtensions.cs b/Emby.Server.Implementations/Services/UrlExtensions.cs
index e3b6aa197..92e36b60e 100644
--- a/Emby.Server.Implementations/Services/UrlExtensions.cs
+++ b/Emby.Server.Implementations/Services/UrlExtensions.cs
@@ -9,7 +9,7 @@ namespace Emby.Server.Implementations.Services
/// Donated by Ivan Korneliuk from his post:
/// http://korneliuk.blogspot.com/2012/08/servicestack-reusing-dtos.html
///
- /// Modified to only allow using routes matching the supplied HTTP Verb
+ /// Modified to only allow using routes matching the supplied HTTP Verb.
/// </summary>
public static class UrlExtensions
{
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index 07e443ef5..ca9f95c70 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -296,7 +296,7 @@ namespace Emby.Server.Implementations.Session
}
catch (DbUpdateConcurrencyException e)
{
- _logger.LogWarning(e, "Error updating user's last activity date.");
+ _logger.LogDebug(e, "Error updating user's last activity date.");
}
}
}
@@ -502,7 +502,8 @@ namespace Emby.Server.Implementations.Session
Client = appName,
DeviceId = deviceId,
ApplicationVersion = appVersion,
- Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture)
+ Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture),
+ ServerId = _appHost.SystemId
};
var username = user?.Username;
@@ -843,7 +844,7 @@ namespace Emby.Server.Implementations.Session
}
/// <summary>
- /// Used to report that playback has ended for an item
+ /// Used to report that playback has ended for an item.
/// </summary>
/// <param name="info">The info.</param>
/// <returns>Task.</returns>
diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
index ef32c692c..b9db6ecd0 100644
--- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
+++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
@@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Session
{
/// <summary>
- /// Class SessionWebSocketListener
+ /// Class SessionWebSocketListener.
/// </summary>
public sealed class SessionWebSocketListener : IWebSocketListener, IDisposable
{
@@ -34,12 +34,12 @@ namespace Emby.Server.Implementations.Session
public const float ForceKeepAliveFactor = 0.75f;
/// <summary>
- /// The _session manager
+ /// The _session manager.
/// </summary>
private readonly ISessionManager _sessionManager;
/// <summary>
- /// The _logger
+ /// The _logger.
/// </summary>
private readonly ILogger<SessionWebSocketListener> _logger;
private readonly ILoggerFactory _loggerFactory;
@@ -167,6 +167,7 @@ namespace Emby.Server.Implementations.Session
_logger.LogWarning("Multiple attempts to keep alive single WebSocket {0}", webSocket);
return;
}
+
webSocket.Closed += OnWebSocketClosed;
webSocket.LastKeepAliveDate = DateTime.UtcNow;
diff --git a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
index 67e31f7f6..2b7d818be 100644
--- a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
+++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
@@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.Sorting
if (val != 0)
{
- //return val;
+ // return val;
}
}
diff --git a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs
index 0804b01fc..7657cc74e 100644
--- a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs
+++ b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs
@@ -8,7 +8,7 @@ using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{
/// <summary>
- /// Class AlbumArtistComparer
+ /// Class AlbumArtistComparer.
/// </summary>
public class AlbumArtistComparer : IBaseItemComparer
{
diff --git a/Emby.Server.Implementations/Sorting/AlbumComparer.cs b/Emby.Server.Implementations/Sorting/AlbumComparer.cs
index 3831a0d2d..7dfdd9ecf 100644
--- a/Emby.Server.Implementations/Sorting/AlbumComparer.cs
+++ b/Emby.Server.Implementations/Sorting/AlbumComparer.cs
@@ -7,7 +7,7 @@ using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{
/// <summary>
- /// Class AlbumComparer
+ /// Class AlbumComparer.
/// </summary>
public class AlbumComparer : IBaseItemComparer
{
diff --git a/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs b/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs
index adb78dec5..fa136c36d 100644
--- a/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs
+++ b/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs
@@ -5,7 +5,7 @@ using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{
/// <summary>
- /// Class CriticRatingComparer
+ /// Class CriticRatingComparer.
/// </summary>
public class CriticRatingComparer : IBaseItemComparer
{
diff --git a/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs
index 8501bd9ee..cbca300d2 100644
--- a/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs
+++ b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs
@@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{
/// <summary>
- /// Class DateCreatedComparer
+ /// Class DateCreatedComparer.
/// </summary>
public class DateCreatedComparer : IBaseItemComparer
{
@@ -19,10 +19,14 @@ namespace Emby.Server.Implementations.Sorting
public int Compare(BaseItem x, BaseItem y)
{
if (x == null)
+ {
throw new ArgumentNullException(nameof(x));
+ }
if (y == null)
+ {
throw new ArgumentNullException(nameof(y));
+ }
return DateTime.Compare(x.DateCreated, y.DateCreated);
}
diff --git a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs
index 5e527ea25..16bd2aff8 100644
--- a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs
+++ b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs
@@ -8,7 +8,7 @@ using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{
/// <summary>
- /// Class DatePlayedComparer
+ /// Class DatePlayedComparer.
/// </summary>
public class DatePlayedComparer : IUserBaseItemComparer
{
diff --git a/Emby.Server.Implementations/Sorting/NameComparer.cs b/Emby.Server.Implementations/Sorting/NameComparer.cs
index 4eb1549f5..da020d8d8 100644
--- a/Emby.Server.Implementations/Sorting/NameComparer.cs
+++ b/Emby.Server.Implementations/Sorting/NameComparer.cs
@@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{
/// <summary>
- /// Class NameComparer
+ /// Class NameComparer.
/// </summary>
public class NameComparer : IBaseItemComparer
{
diff --git a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs
index afbaaf6ab..5c2830322 100644
--- a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs
+++ b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs
@@ -7,7 +7,7 @@ using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{
/// <summary>
- /// Class PlayCountComparer
+ /// Class PlayCountComparer.
/// </summary>
public class PlayCountComparer : IUserBaseItemComparer
{
diff --git a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs
index 0c944a7a0..92ac04dc6 100644
--- a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs
+++ b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs
@@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{
/// <summary>
- /// Class PremiereDateComparer
+ /// Class PremiereDateComparer.
/// </summary>
public class PremiereDateComparer : IBaseItemComparer
{
@@ -44,6 +44,7 @@ namespace Emby.Server.Implementations.Sorting
// 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
index 472a07eb3..e2857df0b 100644
--- a/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs
+++ b/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs
@@ -5,7 +5,7 @@ using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{
/// <summary>
- /// Class ProductionYearComparer
+ /// Class ProductionYearComparer.
/// </summary>
public class ProductionYearComparer : IBaseItemComparer
{
diff --git a/Emby.Server.Implementations/Sorting/RandomComparer.cs b/Emby.Server.Implementations/Sorting/RandomComparer.cs
index bde8b4534..7739d0418 100644
--- a/Emby.Server.Implementations/Sorting/RandomComparer.cs
+++ b/Emby.Server.Implementations/Sorting/RandomComparer.cs
@@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{
/// <summary>
- /// Class RandomComparer
+ /// Class RandomComparer.
/// </summary>
public class RandomComparer : IBaseItemComparer
{
diff --git a/Emby.Server.Implementations/Sorting/RuntimeComparer.cs b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs
index 1d2bdde26..dde44333d 100644
--- a/Emby.Server.Implementations/Sorting/RuntimeComparer.cs
+++ b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs
@@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{
/// <summary>
- /// Class RuntimeComparer
+ /// Class RuntimeComparer.
/// </summary>
public class RuntimeComparer : IBaseItemComparer
{
@@ -19,10 +19,14 @@ namespace Emby.Server.Implementations.Sorting
public int Compare(BaseItem x, BaseItem y)
{
if (x == null)
+ {
throw new ArgumentNullException(nameof(x));
+ }
if (y == null)
+ {
throw new ArgumentNullException(nameof(y));
+ }
return (x.RunTimeTicks ?? 0).CompareTo(y.RunTimeTicks ?? 0);
}
diff --git a/Emby.Server.Implementations/Sorting/SortNameComparer.cs b/Emby.Server.Implementations/Sorting/SortNameComparer.cs
index cc0571c78..f745e193b 100644
--- a/Emby.Server.Implementations/Sorting/SortNameComparer.cs
+++ b/Emby.Server.Implementations/Sorting/SortNameComparer.cs
@@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{
/// <summary>
- /// Class SortNameComparer
+ /// Class SortNameComparer.
/// </summary>
public class SortNameComparer : IBaseItemComparer
{
@@ -19,10 +19,14 @@ namespace Emby.Server.Implementations.Sorting
public int Compare(BaseItem x, BaseItem y)
{
if (x == null)
+ {
throw new ArgumentNullException(nameof(x));
+ }
if (y == null)
+ {
throw new ArgumentNullException(nameof(y));
+ }
return string.Compare(x.SortName, y.SortName, StringComparison.CurrentCultureIgnoreCase);
}
diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs
index d0812a13f..39d17833f 100644
--- a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs
+++ b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs
@@ -102,20 +102,15 @@ namespace Emby.Server.Implementations.SyncPlay
return new SessionInfo[] { from };
case BroadcastType.AllGroup:
return _group.Participants.Values.Select(
- session => session.Session
- ).ToArray();
+ session => session.Session).ToArray();
case BroadcastType.AllExceptCurrentSession:
return _group.Participants.Values.Select(
- session => session.Session
- ).Where(
- session => !session.Id.Equals(from.Id)
- ).ToArray();
+ session => session.Session).Where(
+ session => !session.Id.Equals(from.Id)).ToArray();
case BroadcastType.AllReady:
return _group.Participants.Values.Where(
- session => !session.IsBuffering
- ).Select(
- session => session.Session
- ).ToArray();
+ session => !session.IsBuffering).Select(
+ session => session.Session).ToArray();
default:
return Array.Empty<SessionInfo>();
}
@@ -199,26 +194,24 @@ namespace Emby.Server.Implementations.SyncPlay
}
/// <inheritdoc />
- public void InitGroup(SessionInfo session, CancellationToken cancellationToken)
+ public void CreateGroup(SessionInfo session, CancellationToken cancellationToken)
{
_group.AddSession(session);
_syncPlayManager.AddSessionToGroup(session, this);
_group.PlayingItem = session.FullNowPlayingItem;
- _group.IsPaused = true;
+ _group.IsPaused = session.PlayState.IsPaused;
_group.PositionTicks = session.PlayState.PositionTicks ?? 0;
_group.LastActivity = DateTime.UtcNow;
var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow));
SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken);
- var pauseCommand = NewSyncPlayCommand(SendCommandType.Pause);
- SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken);
}
/// <inheritdoc />
public void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken)
{
- if (session.NowPlayingItem?.Id == _group.PlayingItem.Id && request.PlayingItemId == _group.PlayingItem.Id)
+ if (session.NowPlayingItem?.Id == _group.PlayingItem.Id)
{
_group.AddSession(session);
_syncPlayManager.AddSessionToGroup(session, this);
@@ -229,7 +222,7 @@ namespace Emby.Server.Implementations.SyncPlay
var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserJoined, session.UserName);
SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
- // Client join and play, syncing will happen client side
+ // Syncing will happen client-side
if (!_group.IsPaused)
{
var playCommand = NewSyncPlayCommand(SendCommandType.Play);
@@ -267,10 +260,9 @@ namespace Emby.Server.Implementations.SyncPlay
/// <inheritdoc />
public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
{
- // The server's job is to mantain a consistent state to which clients refer to,
- // as also to 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.
+ // 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.
switch (request.Type)
{
case PlaybackRequestType.Play:
@@ -282,13 +274,13 @@ namespace Emby.Server.Implementations.SyncPlay
case PlaybackRequestType.Seek:
HandleSeekRequest(session, request, cancellationToken);
break;
- case PlaybackRequestType.Buffering:
+ case PlaybackRequestType.Buffer:
HandleBufferingRequest(session, request, cancellationToken);
break;
- case PlaybackRequestType.BufferingDone:
+ case PlaybackRequestType.Ready:
HandleBufferingDoneRequest(session, request, cancellationToken);
break;
- case PlaybackRequestType.UpdatePing:
+ case PlaybackRequestType.Ping:
HandlePingUpdateRequest(session, request);
break;
}
@@ -306,7 +298,7 @@ namespace Emby.Server.Implementations.SyncPlay
{
// Pick a suitable time that accounts for latency
var delay = _group.GetHighestPing() * 2;
- delay = delay < _group.DefaulPing ? _group.DefaulPing : delay;
+ delay = delay < _group.DefaultPing ? _group.DefaultPing : delay;
// Unpause group and set starting point in future
// Clients will start playback at LastActivity (datetime) from PositionTicks (playback position)
@@ -314,8 +306,7 @@ namespace Emby.Server.Implementations.SyncPlay
// Playback synchronization will mainly happen client side
_group.IsPaused = false;
_group.LastActivity = DateTime.UtcNow.AddMilliseconds(
- delay
- );
+ delay);
var command = NewSyncPlayCommand(SendCommandType.Play);
SendCommand(session, BroadcastType.AllGroup, command, cancellationToken);
@@ -343,8 +334,9 @@ namespace Emby.Server.Implementations.SyncPlay
var currentTime = DateTime.UtcNow;
var elapsedTime = currentTime - _group.LastActivity;
_group.LastActivity = currentTime;
+
// Seek only if playback actually started
- // (a pause request may be issued during the delay added to account for latency)
+ // Pause request may be issued during the delay added to account for latency
_group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0;
var command = NewSyncPlayCommand(SendCommandType.Pause);
@@ -449,8 +441,7 @@ namespace Emby.Server.Implementations.SyncPlay
{
// Client that was buffering is recovering, notifying others to resume
_group.LastActivity = currentTime.AddMilliseconds(
- delay
- );
+ delay);
var command = NewSyncPlayCommand(SendCommandType.Play);
SendCommand(session, BroadcastType.AllExceptCurrentSession, command, cancellationToken);
}
@@ -458,11 +449,10 @@ namespace Emby.Server.Implementations.SyncPlay
{
// Client, that was buffering, resumed playback but did not update others in time
delay = _group.GetHighestPing() * 2;
- delay = delay < _group.DefaulPing ? _group.DefaulPing : delay;
+ delay = delay < _group.DefaultPing ? _group.DefaultPing : delay;
_group.LastActivity = currentTime.AddMilliseconds(
- delay
- );
+ delay);
var command = NewSyncPlayCommand(SendCommandType.Play);
SendCommand(session, BroadcastType.AllGroup, command, cancellationToken);
@@ -503,7 +493,7 @@ namespace Emby.Server.Implementations.SyncPlay
private void HandlePingUpdateRequest(SessionInfo session, PlaybackRequest request)
{
// Collected pings are used to account for network latency when unpausing playback
- _group.UpdatePing(session, request.Ping ?? _group.DefaulPing);
+ _group.UpdatePing(session, request.Ping ?? _group.DefaultPing);
}
/// <inheritdoc />
diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
index ecc3eeb39..966ed5024 100644
--- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
+++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
@@ -170,10 +170,11 @@ namespace Emby.Server.Implementations.SyncPlay
{
_logger.LogWarning("NewGroup: {0} does not have permission to create groups.", session.Id);
- var error = new GroupUpdate<string>()
+ var error = new GroupUpdate<string>
{
Type = GroupUpdateType.CreateGroupDenied
};
+
_sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
return;
}
@@ -188,7 +189,7 @@ namespace Emby.Server.Implementations.SyncPlay
var group = new SyncPlayController(_sessionManager, this);
_groups[group.GetGroupId()] = group;
- group.InitGroup(session, cancellationToken);
+ group.CreateGroup(session, cancellationToken);
}
}
@@ -205,6 +206,7 @@ namespace Emby.Server.Implementations.SyncPlay
{
Type = GroupUpdateType.JoinGroupDenied
};
+
_sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
return;
}
@@ -297,19 +299,15 @@ namespace Emby.Server.Implementations.SyncPlay
if (!filterItemId.Equals(Guid.Empty))
{
return _groups.Values.Where(
- group => group.GetPlayingItemId().Equals(filterItemId) && HasAccessToItem(user, group.GetPlayingItemId())
- ).Select(
- group => group.GetInfo()
- ).ToList();
+ group => group.GetPlayingItemId().Equals(filterItemId) && HasAccessToItem(user, group.GetPlayingItemId())).Select(
+ group => group.GetInfo()).ToList();
}
- // Otherwise show all available groups
else
{
+ // Otherwise show all available groups
return _groups.Values.Where(
- group => HasAccessToItem(user, group.GetPlayingItemId())
- ).Select(
- group => group.GetInfo()
- ).ToList();
+ group => HasAccessToItem(user, group.GetPlayingItemId())).Select(
+ group => group.GetInfo()).ToList();
}
}
@@ -326,6 +324,7 @@ namespace Emby.Server.Implementations.SyncPlay
{
Type = GroupUpdateType.JoinGroupDenied
};
+
_sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
return;
}
@@ -370,7 +369,6 @@ namespace Emby.Server.Implementations.SyncPlay
}
_sessionToGroupMap.Remove(session.Id, out var tempGroup);
-
if (!tempGroup.GetGroupId().Equals(group.GetGroupId()))
{
throw new InvalidOperationException("Session was in wrong group!");
diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs
index 6999668d1..d1818deff 100644
--- a/Emby.Server.Implementations/TV/TVSeriesManager.cs
+++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs
@@ -117,23 +117,20 @@ namespace Emby.Server.Implementations.TV
limit = limit.Value + 10;
}
- var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
- {
- IncludeItemTypes = new[] { typeof(Episode).Name },
- OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.DatePlayed, SortOrder.Descending) },
- SeriesPresentationUniqueKey = presentationUniqueKey,
- Limit = limit,
- DtoOptions = new DtoOptions
- {
- Fields = new ItemFields[]
+ var items = _libraryManager
+ .GetItemList(
+ new InternalItemsQuery(user)
{
- ItemFields.SeriesPresentationUniqueKey
- },
- EnableImages = false
- },
- GroupBySeriesPresentationUniqueKey = true
-
- }, parentsFolders.ToList()).Cast<Episode>().Select(GetUniqueSeriesKey);
+ IncludeItemTypes = new[] { typeof(Episode).Name },
+ OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.DatePlayed, SortOrder.Descending) },
+ SeriesPresentationUniqueKey = presentationUniqueKey,
+ Limit = limit,
+ DtoOptions = new DtoOptions { Fields = new[] { ItemFields.SeriesPresentationUniqueKey }, EnableImages = false },
+ GroupBySeriesPresentationUniqueKey = true
+ }, parentsFolders.ToList())
+ .Cast<Episode>()
+ .Where(episode => !string.IsNullOrEmpty(episode.SeriesPresentationUniqueKey))
+ .Select(GetUniqueSeriesKey);
// Avoid implicitly captured closure
var episodes = GetNextUpEpisodes(request, user, items, dtoOptions);
@@ -149,7 +146,7 @@ namespace Emby.Server.Implementations.TV
var allNextUp = seriesKeys
.Select(i => GetNextUp(i, currentUser, dtoOptions));
- //allNextUp = allNextUp.OrderByDescending(i => i.Item1);
+ // allNextUp = allNextUp.OrderByDescending(i => i.Item1);
// If viewing all next up for all series, remove first episodes
// But if that returns empty, keep those first episodes (avoid completely empty view)
@@ -225,7 +222,6 @@ namespace Emby.Server.Implementations.TV
ParentIndexNumberNotEquals = 0,
MinSortName = lastWatchedEpisode?.SortName,
DtoOptions = dtoOptions
-
}).Cast<Episode>().FirstOrDefault();
};
@@ -257,6 +253,7 @@ namespace Emby.Server.Implementations.TV
{
items = items.Skip(query.StartIndex.Value);
}
+
if (query.Limit.HasValue)
{
items = items.Take(query.Limit.Value);
diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs
index a26f714b1..73fcbcec3 100644
--- a/Emby.Server.Implementations/Udp/UdpServer.cs
+++ b/Emby.Server.Implementations/Udp/UdpServer.cs
@@ -18,7 +18,7 @@ namespace Emby.Server.Implementations.Udp
public sealed class UdpServer : IDisposable
{
/// <summary>
- /// The _logger
+ /// The _logger.
/// </summary>
private readonly ILogger _logger;
private readonly IServerApplicationHost _appHost;
@@ -101,11 +101,18 @@ namespace Emby.Server.Implementations.Udp
{
while (!cancellationToken.IsCancellationRequested)
{
+ var infiniteTask = Task.Delay(-1, cancellationToken);
try
{
- var result = await _udpSocket.ReceiveFromAsync(_receiveBuffer, SocketFlags.None, _endpoint).ConfigureAwait(false);
+ var task = _udpSocket.ReceiveFromAsync(_receiveBuffer, SocketFlags.None, _endpoint);
+ await Task.WhenAny(task, infiniteTask).ConfigureAwait(false);
- cancellationToken.ThrowIfCancellationRequested();
+ if (!task.IsCompleted)
+ {
+ return;
+ }
+
+ var result = task.Result;
var text = Encoding.UTF8.GetString(_receiveBuffer, 0, result.ReceivedBytes);
if (text.Contains("who is JellyfinServer?", StringComparison.OrdinalIgnoreCase))
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index 80326fddf..4f54c06dd 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -1,7 +1,6 @@
#pragma warning disable CS1591
using System;
-using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
@@ -17,11 +16,10 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Model.Events;
using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Updates;
-using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Updates
@@ -32,11 +30,6 @@ namespace Emby.Server.Implementations.Updates
public class InstallationManager : IInstallationManager
{
/// <summary>
- /// The key for a setting that specifies a URL for the plugin repository JSON manifest.
- /// </summary>
- public const string PluginManifestUrlKey = "InstallationManager:PluginManifestUrl";
-
- /// <summary>
/// The logger.
/// </summary>
private readonly ILogger<InstallationManager> _logger;
@@ -53,7 +46,6 @@ namespace Emby.Server.Implementations.Updates
private readonly IApplicationHost _applicationHost;
private readonly IZipClient _zipClient;
- private readonly IConfiguration _appConfig;
private readonly object _currentInstallationsLock = new object();
@@ -75,8 +67,7 @@ namespace Emby.Server.Implementations.Updates
IJsonSerializer jsonSerializer,
IServerConfigurationManager config,
IFileSystem fileSystem,
- IZipClient zipClient,
- IConfiguration appConfig)
+ IZipClient zipClient)
{
if (logger == null)
{
@@ -94,7 +85,6 @@ namespace Emby.Server.Implementations.Updates
_config = config;
_fileSystem = fileSystem;
_zipClient = zipClient;
- _appConfig = appConfig;
}
/// <inheritdoc />
@@ -122,16 +112,14 @@ namespace Emby.Server.Implementations.Updates
public IEnumerable<InstallationInfo> CompletedInstallations => _completedInstallationsInternal;
/// <inheritdoc />
- public async Task<IReadOnlyList<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken = default)
+ public async Task<IReadOnlyList<PackageInfo>> GetPackages(string manifest, CancellationToken cancellationToken = default)
{
- var manifestUrl = _appConfig.GetValue<string>(PluginManifestUrlKey);
-
try
{
using (var response = await _httpClient.SendAsync(
new HttpRequestOptions
{
- Url = manifestUrl,
+ Url = manifest,
CancellationToken = cancellationToken,
CacheMode = CacheMode.Unconditional,
CacheLength = TimeSpan.FromMinutes(3)
@@ -145,23 +133,38 @@ namespace Emby.Server.Implementations.Updates
}
catch (SerializationException ex)
{
- const string LogTemplate =
- "Failed to deserialize the plugin manifest retrieved from {PluginManifestUrl}. If you " +
- "have specified a custom plugin repository manifest URL with --plugin-manifest-url or " +
- PluginManifestUrlKey + ", please ensure that it is correct.";
- _logger.LogError(ex, LogTemplate, manifestUrl);
- throw;
+ _logger.LogError(ex, "Failed to deserialize the plugin manifest retrieved from {Manifest}", manifest);
+ return Array.Empty<PackageInfo>();
}
}
}
catch (UriFormatException ex)
{
- const string LogTemplate =
- "The URL configured for the plugin repository manifest URL is not valid: {PluginManifestUrl}. " +
- "Please check the URL configured by --plugin-manifest-url or " + PluginManifestUrlKey;
- _logger.LogError(ex, LogTemplate, manifestUrl);
- throw;
+ _logger.LogError(ex, "The URL configured for the plugin repository manifest URL is not valid: {Manifest}", manifest);
+ return Array.Empty<PackageInfo>();
+ }
+ catch (HttpException ex)
+ {
+ _logger.LogError(ex, "An error occurred while accessing the plugin manifest: {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)
+ {
+ result.AddRange(await GetPackages(repository.Url, cancellationToken).ConfigureAwait(true));
}
+
+ return result;
}
/// <inheritdoc />
@@ -402,6 +405,12 @@ namespace Emby.Server.Implementations.Updates
/// <param name="plugin">The plugin.</param>
public void UninstallPlugin(IPlugin plugin)
{
+ if (!plugin.CanUninstall)
+ {
+ _logger.LogWarning("Attempt to delete non removable plugin {0}, ignoring request", plugin.Name);
+ return;
+ }
+
plugin.OnUninstalling();
// Remove it the quick way for now
diff --git a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs b/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
index 50b6468db..9fde175d0 100644
--- a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
+++ b/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
@@ -52,7 +52,7 @@ namespace Jellyfin.Api.Auth
{
// Ensure claim has userId.
var userId = ClaimHelpers.GetUserId(claimsPrincipal);
- if (userId == null)
+ if (!userId.HasValue)
{
return false;
}
diff --git a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs
index ea02e6a0b..733c6959e 100644
--- a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs
+++ b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs
@@ -54,7 +54,7 @@ namespace Jellyfin.Api.Auth
var claims = new[]
{
new Claim(ClaimTypes.Name, authorizationInfo.User.Username),
- new Claim(ClaimTypes.Role, value: authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User),
+ new Claim(ClaimTypes.Role, authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User),
new Claim(InternalClaimTypes.UserId, authorizationInfo.UserId.ToString("N", CultureInfo.InvariantCulture)),
new Claim(InternalClaimTypes.DeviceId, authorizationInfo.DeviceId),
new Claim(InternalClaimTypes.Device, authorizationInfo.Device),
diff --git a/Jellyfin.Api/Auth/DownloadPolicy/DownloadHandler.cs b/Jellyfin.Api/Auth/DownloadPolicy/DownloadHandler.cs
index fcfa55dfe..b61680ab1 100644
--- a/Jellyfin.Api/Auth/DownloadPolicy/DownloadHandler.cs
+++ b/Jellyfin.Api/Auth/DownloadPolicy/DownloadHandler.cs
@@ -1,5 +1,4 @@
using System.Threading.Tasks;
-using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization;
diff --git a/Jellyfin.Api/Controllers/ActivityLogController.cs b/Jellyfin.Api/Controllers/ActivityLogController.cs
index c287d1a77..12ea24973 100644
--- a/Jellyfin.Api/Controllers/ActivityLogController.cs
+++ b/Jellyfin.Api/Controllers/ActivityLogController.cs
@@ -1,5 +1,4 @@
using System;
-using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Data.Entities;
@@ -35,6 +34,7 @@ namespace Jellyfin.Api.Controllers
/// <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="minDate">Optional. The minimum date. Format = ISO.</param>
+ /// <param name="hasUserId">Optional. Filter log entries if it has user id, or not.</param>
/// <response code="200">Activity log returned.</response>
/// <returns>A <see cref="QueryResult{ActivityLogEntry}"/> containing the log entries.</returns>
[HttpGet("Entries")]
@@ -42,10 +42,14 @@ namespace Jellyfin.Api.Controllers
public ActionResult<QueryResult<ActivityLogEntry>> GetLogEntries(
[FromQuery] int? startIndex,
[FromQuery] int? limit,
- [FromQuery] DateTime? minDate)
+ [FromQuery] DateTime? minDate,
+ [FromQuery] bool? hasUserId)
{
var filterFunc = new Func<IQueryable<ActivityLog>, IQueryable<ActivityLog>>(
- entries => entries.Where(entry => entry.DateCreated >= minDate));
+ entries => entries.Where(entry => entry.DateCreated >= minDate
+ && (!hasUserId.HasValue || (hasUserId.Value
+ ? entry.UserId != Guid.Empty
+ : entry.UserId == Guid.Empty))));
return _activityManager.GetPagedResult(filterFunc, startIndex, limit);
}
diff --git a/Jellyfin.Api/Controllers/AlbumsController.cs b/Jellyfin.Api/Controllers/AlbumsController.cs
index 622123873..01ba7fc32 100644
--- a/Jellyfin.Api/Controllers/AlbumsController.cs
+++ b/Jellyfin.Api/Controllers/AlbumsController.cs
@@ -52,8 +52,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSimilarAlbums(
[FromRoute] string albumId,
- [FromQuery] Guid userId,
- [FromQuery] string excludeArtistIds,
+ [FromQuery] Guid? userId,
+ [FromQuery] string? excludeArtistIds,
[FromQuery] int? limit)
{
var dtoOptions = new DtoOptions().AddClientFields(Request);
@@ -84,8 +84,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSimilarArtists(
[FromRoute] string artistId,
- [FromQuery] Guid userId,
- [FromQuery] string excludeArtistIds,
+ [FromQuery] Guid? userId,
+ [FromQuery] string? excludeArtistIds,
[FromQuery] int? limit)
{
var dtoOptions = new DtoOptions().AddClientFields(Request);
diff --git a/Jellyfin.Api/Controllers/ApiKeyController.cs b/Jellyfin.Api/Controllers/ApiKeyController.cs
index ed521c1fc..fef4d7262 100644
--- a/Jellyfin.Api/Controllers/ApiKeyController.cs
+++ b/Jellyfin.Api/Controllers/ApiKeyController.cs
@@ -65,7 +65,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Keys")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult CreateKey([FromQuery, Required] string app)
+ public ActionResult CreateKey([FromQuery, Required] string? app)
{
_authRepo.Create(new AuthenticationInfo
{
@@ -88,7 +88,7 @@ namespace Jellyfin.Api.Controllers
[HttpDelete("Keys/{key}")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult RevokeKey([FromRoute] string key)
+ public ActionResult RevokeKey([FromRoute] string? key)
{
_sessionManager.RevokeToken(key);
return NoContent();
diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs
new file mode 100644
index 000000000..d39021446
--- /dev/null
+++ b/Jellyfin.Api/Controllers/ArtistsController.cs
@@ -0,0 +1,488 @@
+using System;
+using System.Linq;
+using Jellyfin.Api.Constants;
+using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
+using Jellyfin.Data.Entities;
+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 artists controller.
+ /// </summary>
+ [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Route("/Artists")]
+ 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. 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 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. 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="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="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] string? parentId,
+ [FromQuery] string? fields,
+ [FromQuery] string? excludeItemTypes,
+ [FromQuery] string? includeItemTypes,
+ [FromQuery] string? filters,
+ [FromQuery] bool? isFavorite,
+ [FromQuery] string? mediaTypes,
+ [FromQuery] string? genres,
+ [FromQuery] string? genreIds,
+ [FromQuery] string? officialRatings,
+ [FromQuery] string? tags,
+ [FromQuery] string? years,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] int? imageTypeLimit,
+ [FromQuery] string? enableImageTypes,
+ [FromQuery] string? person,
+ [FromQuery] string? personIds,
+ [FromQuery] string? personTypes,
+ [FromQuery] string? studios,
+ [FromQuery] string? studioIds,
+ [FromQuery] Guid? userId,
+ [FromQuery] string? nameStartsWithOrGreater,
+ [FromQuery] string? nameStartsWith,
+ [FromQuery] string? nameLessThan,
+ [FromQuery] bool? enableImages = true,
+ [FromQuery] bool enableTotalRecordCount = true)
+ {
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request)
+ .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
+
+ User? user = null;
+ BaseItem parentItem;
+
+ if (userId.HasValue && !userId.Equals(Guid.Empty))
+ {
+ user = _userManager.GetUserById(userId.Value);
+ parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
+ }
+ else
+ {
+ parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId);
+ }
+
+ var excludeItemTypesArr = RequestHelpers.Split(excludeItemTypes, ',', true);
+ var includeItemTypesArr = RequestHelpers.Split(includeItemTypes, ',', true);
+ var mediaTypesArr = RequestHelpers.Split(mediaTypes, ',', true);
+
+ var query = new InternalItemsQuery(user)
+ {
+ ExcludeItemTypes = excludeItemTypesArr,
+ IncludeItemTypes = includeItemTypesArr,
+ MediaTypes = mediaTypesArr,
+ StartIndex = startIndex,
+ Limit = limit,
+ IsFavorite = isFavorite,
+ NameLessThan = nameLessThan,
+ NameStartsWith = nameStartsWith,
+ NameStartsWithOrGreater = nameStartsWithOrGreater,
+ Tags = RequestHelpers.Split(tags, ',', true),
+ OfficialRatings = RequestHelpers.Split(officialRatings, ',', true),
+ Genres = RequestHelpers.Split(genres, ',', true),
+ GenreIds = RequestHelpers.GetGuids(genreIds),
+ StudioIds = RequestHelpers.GetGuids(studioIds),
+ Person = person,
+ PersonIds = RequestHelpers.GetGuids(personIds),
+ PersonTypes = RequestHelpers.Split(personTypes, ',', true),
+ Years = RequestHelpers.Split(years, ',', true).Select(int.Parse).ToArray(),
+ MinCommunityRating = minCommunityRating,
+ DtoOptions = dtoOptions,
+ SearchTerm = searchTerm,
+ EnableTotalRecordCount = enableTotalRecordCount
+ };
+
+ if (!string.IsNullOrWhiteSpace(parentId))
+ {
+ if (parentItem is Folder)
+ {
+ query.AncestorIds = new[] { new Guid(parentId) };
+ }
+ else
+ {
+ query.ItemIds = new[] { new Guid(parentId) };
+ }
+ }
+
+ // Studios
+ if (!string.IsNullOrEmpty(studios))
+ {
+ query.StudioIds = studios.Split('|').Select(i =>
+ {
+ try
+ {
+ return _libraryManager.GetStudio(i);
+ }
+ catch
+ {
+ return null;
+ }
+ }).Where(i => i != null).Select(i => i!.Id).ToArray();
+ }
+
+ foreach (var filter in RequestHelpers.GetFilters(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 (!string.IsNullOrWhiteSpace(includeItemTypes))
+ {
+ 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>
+ {
+ Items = dtos.ToArray(),
+ TotalRecordCount = result.TotalRecordCount
+ };
+ }
+
+ /// <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. 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 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. 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="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="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] string? parentId,
+ [FromQuery] string? fields,
+ [FromQuery] string? excludeItemTypes,
+ [FromQuery] string? includeItemTypes,
+ [FromQuery] string? filters,
+ [FromQuery] bool? isFavorite,
+ [FromQuery] string? mediaTypes,
+ [FromQuery] string? genres,
+ [FromQuery] string? genreIds,
+ [FromQuery] string? officialRatings,
+ [FromQuery] string? tags,
+ [FromQuery] string? years,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] int? imageTypeLimit,
+ [FromQuery] string? enableImageTypes,
+ [FromQuery] string? person,
+ [FromQuery] string? personIds,
+ [FromQuery] string? personTypes,
+ [FromQuery] string? studios,
+ [FromQuery] string? studioIds,
+ [FromQuery] Guid? userId,
+ [FromQuery] string? nameStartsWithOrGreater,
+ [FromQuery] string? nameStartsWith,
+ [FromQuery] string? nameLessThan,
+ [FromQuery] bool? enableImages = true,
+ [FromQuery] bool enableTotalRecordCount = true)
+ {
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request)
+ .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
+
+ User? user = null;
+ BaseItem parentItem;
+
+ if (userId.HasValue && !userId.Equals(Guid.Empty))
+ {
+ user = _userManager.GetUserById(userId.Value);
+ parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
+ }
+ else
+ {
+ parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId);
+ }
+
+ var excludeItemTypesArr = RequestHelpers.Split(excludeItemTypes, ',', true);
+ var includeItemTypesArr = RequestHelpers.Split(includeItemTypes, ',', true);
+ var mediaTypesArr = RequestHelpers.Split(mediaTypes, ',', true);
+
+ var query = new InternalItemsQuery(user)
+ {
+ ExcludeItemTypes = excludeItemTypesArr,
+ IncludeItemTypes = includeItemTypesArr,
+ MediaTypes = mediaTypesArr,
+ StartIndex = startIndex,
+ Limit = limit,
+ IsFavorite = isFavorite,
+ NameLessThan = nameLessThan,
+ NameStartsWith = nameStartsWith,
+ NameStartsWithOrGreater = nameStartsWithOrGreater,
+ Tags = RequestHelpers.Split(tags, ',', true),
+ OfficialRatings = RequestHelpers.Split(officialRatings, ',', true),
+ Genres = RequestHelpers.Split(genres, ',', true),
+ GenreIds = RequestHelpers.GetGuids(genreIds),
+ StudioIds = RequestHelpers.GetGuids(studioIds),
+ Person = person,
+ PersonIds = RequestHelpers.GetGuids(personIds),
+ PersonTypes = RequestHelpers.Split(personTypes, ',', true),
+ Years = RequestHelpers.Split(years, ',', true).Select(int.Parse).ToArray(),
+ MinCommunityRating = minCommunityRating,
+ DtoOptions = dtoOptions,
+ SearchTerm = searchTerm,
+ EnableTotalRecordCount = enableTotalRecordCount
+ };
+
+ if (!string.IsNullOrWhiteSpace(parentId))
+ {
+ if (parentItem is Folder)
+ {
+ query.AncestorIds = new[] { new Guid(parentId) };
+ }
+ else
+ {
+ query.ItemIds = new[] { new Guid(parentId) };
+ }
+ }
+
+ // Studios
+ if (!string.IsNullOrEmpty(studios))
+ {
+ query.StudioIds = studios.Split('|').Select(i =>
+ {
+ try
+ {
+ return _libraryManager.GetStudio(i);
+ }
+ catch
+ {
+ return null;
+ }
+ }).Where(i => i != null).Select(i => i!.Id).ToArray();
+ }
+
+ foreach (var filter in RequestHelpers.GetFilters(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 (!string.IsNullOrWhiteSpace(includeItemTypes))
+ {
+ 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>
+ {
+ Items = dtos.ToArray(),
+ TotalRecordCount = result.TotalRecordCount
+ };
+ }
+
+ /// <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] string name, [FromQuery] Guid? userId)
+ {
+ var dtoOptions = new DtoOptions().AddClientFields(Request);
+
+ var item = _libraryManager.GetArtist(name, dtoOptions);
+
+ if (userId.HasValue && !userId.Equals(Guid.Empty))
+ {
+ var user = _userManager.GetUserById(userId.Value);
+
+ return _dtoService.GetBaseItemDto(item, dtoOptions, user);
+ }
+
+ return _dtoService.GetBaseItemDto(item, dtoOptions);
+ }
+ }
+}
diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs
new file mode 100644
index 000000000..bdd7dfd96
--- /dev/null
+++ b/Jellyfin.Api/Controllers/ChannelsController.cs
@@ -0,0 +1,256 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Jellyfin.Api.Constants;
+using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
+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(Policy = Policies.DefaultAuthorization)]
+ 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 ActionResult<QueryResult<BaseItemDto>> GetChannels(
+ [FromQuery] Guid? userId,
+ [FromQuery] int? startIndex,
+ [FromQuery] int? limit,
+ [FromQuery] bool? supportsLatestItems,
+ [FromQuery] bool? supportsMediaDeletion,
+ [FromQuery] bool? isFavorite)
+ {
+ return _channelManager.GetChannels(new ChannelQuery
+ {
+ Limit = limit,
+ StartIndex = startIndex,
+ UserId = userId ?? Guid.Empty,
+ SupportsLatestItems = supportsLatestItems,
+ SupportsMediaDeletion = supportsMediaDeletion,
+ IsFavorite = isFavorite
+ });
+ }
+
+ /// <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] string 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. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.</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. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</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] Guid channelId,
+ [FromQuery] Guid? folderId,
+ [FromQuery] Guid? userId,
+ [FromQuery] int? startIndex,
+ [FromQuery] int? limit,
+ [FromQuery] string? sortOrder,
+ [FromQuery] string? filters,
+ [FromQuery] string? sortBy,
+ [FromQuery] string? fields)
+ {
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+
+ var query = new InternalItemsQuery(user)
+ {
+ Limit = limit,
+ StartIndex = startIndex,
+ ChannelIds = new[] { channelId },
+ ParentId = folderId ?? Guid.Empty,
+ OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder),
+ DtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ };
+
+ foreach (var filter in RequestHelpers.GetFilters(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. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.</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="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] string? filters,
+ [FromQuery] string? fields,
+ [FromQuery] string? channelIds)
+ {
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+
+ var query = new InternalItemsQuery(user)
+ {
+ Limit = limit,
+ StartIndex = startIndex,
+ ChannelIds = (channelIds ?? string.Empty)
+ .Split(',')
+ .Where(i => !string.IsNullOrWhiteSpace(i))
+ .Select(i => new Guid(i))
+ .ToArray(),
+ DtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ };
+
+ foreach (var filter in RequestHelpers.GetFilters(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/CollectionController.cs b/Jellyfin.Api/Controllers/CollectionController.cs
index 29db0b178..6f78a7d84 100644
--- a/Jellyfin.Api/Controllers/CollectionController.cs
+++ b/Jellyfin.Api/Controllers/CollectionController.cs
@@ -44,17 +44,17 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <param name="name">The name of the collection.</param>
/// <param name="ids">Item Ids to add to the collection.</param>
- /// <param name="isLocked">Whether or not to lock the new 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 ActionResult<CollectionCreationResult> CreateCollection(
- [FromQuery] string name,
- [FromQuery] string ids,
- [FromQuery] bool isLocked,
- [FromQuery] Guid? parentId)
+ [FromQuery] string? name,
+ [FromQuery] string? ids,
+ [FromQuery] Guid? parentId,
+ [FromQuery] bool isLocked = false)
{
var userId = _authContext.GetAuthorizationInfo(Request).UserId;
@@ -86,7 +86,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
[HttpPost("{collectionId}/Items")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult AddToCollection([FromRoute] Guid collectionId, [FromQuery] string itemIds)
+ public ActionResult AddToCollection([FromRoute] Guid collectionId, [FromQuery] string? itemIds)
{
_collectionManager.AddToCollection(collectionId, RequestHelpers.Split(itemIds, ',', true));
return NoContent();
@@ -101,7 +101,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
[HttpDelete("{collectionId}/Items")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult RemoveFromCollection([FromRoute] Guid collectionId, [FromQuery] string itemIds)
+ public ActionResult RemoveFromCollection([FromRoute] Guid collectionId, [FromQuery] string? itemIds)
{
_collectionManager.RemoveFromCollection(collectionId, RequestHelpers.Split(itemIds, ',', true));
return NoContent();
diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs
index d275ed2eb..13933cb33 100644
--- a/Jellyfin.Api/Controllers/ConfigurationController.cs
+++ b/Jellyfin.Api/Controllers/ConfigurationController.cs
@@ -70,7 +70,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>Configuration.</returns>
[HttpGet("Configuration/{key}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<object> GetNamedConfiguration([FromRoute] string key)
+ public ActionResult<object> GetNamedConfiguration([FromRoute] string? key)
{
return _configurationManager.GetConfiguration(key);
}
@@ -84,7 +84,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Configuration/{key}")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public async Task<ActionResult> UpdateNamedConfiguration([FromRoute] string key)
+ public async Task<ActionResult> UpdateNamedConfiguration([FromRoute] string? key)
{
var configurationType = _configurationManager.GetConfigurationType(key);
var configuration = await JsonSerializer.DeserializeAsync(Request.Body, configurationType).ConfigureAwait(false);
diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs
index 6cfee2463..e033c50d5 100644
--- a/Jellyfin.Api/Controllers/DashboardController.cs
+++ b/Jellyfin.Api/Controllers/DashboardController.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Jellyfin.Api.Models;
@@ -122,7 +121,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("/web/ConfigurationPage")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult GetDashboardConfigurationPage([FromQuery] string name)
+ public ActionResult GetDashboardConfigurationPage([FromQuery] string? name)
{
IPlugin? plugin = null;
Stream? stream = null;
diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs
index 55ca7b7c0..3cf7b3378 100644
--- a/Jellyfin.Api/Controllers/DevicesController.cs
+++ b/Jellyfin.Api/Controllers/DevicesController.cs
@@ -65,7 +65,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult<DeviceInfo> GetDeviceInfo([FromQuery, BindRequired] string id)
+ public ActionResult<DeviceInfo> GetDeviceInfo([FromQuery, BindRequired] string? id)
{
var deviceInfo = _deviceManager.GetDevice(id);
if (deviceInfo == null)
@@ -87,7 +87,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult<DeviceOptions> GetDeviceOptions([FromQuery, BindRequired] string id)
+ public ActionResult<DeviceOptions> GetDeviceOptions([FromQuery, BindRequired] string? id)
{
var deviceInfo = _deviceManager.GetDeviceOptions(id);
if (deviceInfo == null)
@@ -111,7 +111,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult UpdateDeviceOptions(
- [FromQuery, BindRequired] string id,
+ [FromQuery, BindRequired] string? id,
[FromBody, BindRequired] DeviceOptions deviceOptions)
{
var existingDeviceOptions = _deviceManager.GetDeviceOptions(id);
@@ -134,7 +134,7 @@ namespace Jellyfin.Api.Controllers
[HttpDelete]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult DeleteDevice([FromQuery, BindRequired] string id)
+ public ActionResult DeleteDevice([FromQuery, BindRequired] string? id)
{
var existingDevice = _deviceManager.GetDevice(id);
if (existingDevice == null)
diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
index 3f946d9d2..1255e6dab 100644
--- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
+++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
@@ -39,9 +39,9 @@ namespace Jellyfin.Api.Controllers
[HttpGet("{displayPreferencesId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<DisplayPreferences> GetDisplayPreferences(
- [FromRoute] string displayPreferencesId,
- [FromQuery] [Required] string userId,
- [FromQuery] [Required] string client)
+ [FromRoute] string? displayPreferencesId,
+ [FromQuery] [Required] string? userId,
+ [FromQuery] [Required] string? client)
{
return _displayPreferencesRepository.GetDisplayPreferences(displayPreferencesId, userId, client);
}
@@ -59,9 +59,9 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")]
public ActionResult UpdateDisplayPreferences(
- [FromRoute] string displayPreferencesId,
- [FromQuery, BindRequired] string userId,
- [FromQuery, BindRequired] string client,
+ [FromRoute] string? displayPreferencesId,
+ [FromQuery, BindRequired] string? userId,
+ [FromQuery, BindRequired] string? client,
[FromBody, BindRequired] DisplayPreferences displayPreferences)
{
_displayPreferencesRepository.SaveDisplayPreferences(
diff --git a/Jellyfin.Api/Controllers/EnvironmentController.cs b/Jellyfin.Api/Controllers/EnvironmentController.cs
new file mode 100644
index 000000000..719bb7d86
--- /dev/null
+++ b/Jellyfin.Api/Controllers/EnvironmentController.cs
@@ -0,0 +1,191 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Jellyfin.Api.Constants;
+using Jellyfin.Api.Models.EnvironmentDtos;
+using MediaBrowser.Model.IO;
+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>
+ /// Environment Controller.
+ /// </summary>
+ [Authorize(Policy = Policies.RequiresElevation)]
+ 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, BindRequired] 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="200">Path validated.</response>
+ /// <response code="404">Path not found.</response>
+ /// <returns>Validation status.</returns>
+ [HttpPost("ValidatePath")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public ActionResult ValidatePath([FromBody, BindRequired] 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)
+ {
+ 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 Ok();
+ }
+
+ /// <summary>
+ /// Gets network paths.
+ /// </summary>
+ /// <response code="200">Empty array returned.</response>
+ /// <returns>List of entries.</returns>
+ [Obsolete("This endpoint is obsolete.")]
+ [HttpGet("NetworkShares")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult<IEnumerable<FileSystemEntryInfo>> GetNetworkShares()
+ {
+ _logger.LogWarning("Obsolete endpoint accessed: /Environment/NetworkShares");
+ return Array.Empty<FileSystemEntryInfo>();
+ }
+
+ /// <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, BindRequired] string path)
+ {
+ string? parent = Path.GetDirectoryName(path);
+ if (string.IsNullOrEmpty(parent))
+ {
+ // Check if unc share
+ var index = path.LastIndexOf(UncSeparator);
+
+ if (index != -1 && path.IndexOf(UncSeparator, StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ 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
index 8a0a6ad86..9ba5e1161 100644
--- a/Jellyfin.Api/Controllers/FilterController.cs
+++ b/Jellyfin.Api/Controllers/FilterController.cs
@@ -1,5 +1,4 @@
using System;
-using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Jellyfin.Api.Constants;
using MediaBrowser.Controller.Dto;
@@ -57,9 +56,9 @@ namespace Jellyfin.Api.Controllers
? null
: _libraryManager.GetItemById(parentId);
- var user = userId == null || userId == Guid.Empty
- ? null
- : _userManager.GetUserById(userId.Value);
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
if (string.Equals(includeItemTypes, nameof(BoxSet), StringComparison.OrdinalIgnoreCase)
|| string.Equals(includeItemTypes, nameof(Playlist), StringComparison.OrdinalIgnoreCase)
@@ -152,9 +151,9 @@ namespace Jellyfin.Api.Controllers
? null
: _libraryManager.GetItemById(parentId);
- var user = userId == null || userId == Guid.Empty
- ? null
- : _userManager.GetUserById(userId.Value);
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
if (string.Equals(includeItemTypes, nameof(BoxSet), StringComparison.OrdinalIgnoreCase)
|| string.Equals(includeItemTypes, nameof(Playlist), StringComparison.OrdinalIgnoreCase)
diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs
new file mode 100644
index 000000000..55ad71200
--- /dev/null
+++ b/Jellyfin.Api/Controllers/GenresController.cs
@@ -0,0 +1,320 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using Jellyfin.Api.Constants;
+using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
+using Jellyfin.Data.Entities;
+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;
+using Genre = MediaBrowser.Controller.Entities.Genre;
+
+namespace Jellyfin.Api.Controllers
+{
+ /// <summary>
+ /// The genres controller.
+ /// </summary>
+ [Authorize(Policy = Policies.DefaultAuthorization)]
+ 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="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">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="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="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="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 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="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="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] double? minCommunityRating,
+ [FromQuery] int? startIndex,
+ [FromQuery] int? limit,
+ [FromQuery] string? searchTerm,
+ [FromQuery] string? parentId,
+ [FromQuery] string? fields,
+ [FromQuery] string? excludeItemTypes,
+ [FromQuery] string? includeItemTypes,
+ [FromQuery] string? filters,
+ [FromQuery] bool? isFavorite,
+ [FromQuery] string? mediaTypes,
+ [FromQuery] string? genres,
+ [FromQuery] string? genreIds,
+ [FromQuery] string? officialRatings,
+ [FromQuery] string? tags,
+ [FromQuery] string? years,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] int? imageTypeLimit,
+ [FromQuery] string? enableImageTypes,
+ [FromQuery] string? person,
+ [FromQuery] string? personIds,
+ [FromQuery] string? personTypes,
+ [FromQuery] string? studios,
+ [FromQuery] string? studioIds,
+ [FromQuery] Guid? userId,
+ [FromQuery] string? nameStartsWithOrGreater,
+ [FromQuery] string? nameStartsWith,
+ [FromQuery] string? nameLessThan,
+ [FromQuery] bool? enableImages = true,
+ [FromQuery] bool enableTotalRecordCount = true)
+ {
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request)
+ .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
+
+ User? user = null;
+ BaseItem parentItem;
+
+ if (userId.HasValue && !userId.Equals(Guid.Empty))
+ {
+ user = _userManager.GetUserById(userId.Value);
+ parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
+ }
+ else
+ {
+ parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId);
+ }
+
+ var query = new InternalItemsQuery(user)
+ {
+ ExcludeItemTypes = RequestHelpers.Split(excludeItemTypes, ',', true),
+ IncludeItemTypes = RequestHelpers.Split(includeItemTypes, ',', true),
+ MediaTypes = RequestHelpers.Split(mediaTypes, ',', true),
+ StartIndex = startIndex,
+ Limit = limit,
+ IsFavorite = isFavorite,
+ NameLessThan = nameLessThan,
+ NameStartsWith = nameStartsWith,
+ NameStartsWithOrGreater = nameStartsWithOrGreater,
+ Tags = RequestHelpers.Split(tags, '|', true),
+ OfficialRatings = RequestHelpers.Split(officialRatings, '|', true),
+ Genres = RequestHelpers.Split(genres, '|', true),
+ GenreIds = RequestHelpers.GetGuids(genreIds),
+ StudioIds = RequestHelpers.GetGuids(studioIds),
+ Person = person,
+ PersonIds = RequestHelpers.GetGuids(personIds),
+ PersonTypes = RequestHelpers.Split(personTypes, ',', true),
+ Years = RequestHelpers.Split(years, ',', true).Select(y => Convert.ToInt32(y, CultureInfo.InvariantCulture)).ToArray(),
+ MinCommunityRating = minCommunityRating,
+ DtoOptions = dtoOptions,
+ SearchTerm = searchTerm,
+ EnableTotalRecordCount = enableTotalRecordCount
+ };
+
+ if (!string.IsNullOrWhiteSpace(parentId))
+ {
+ if (parentItem is Folder)
+ {
+ query.AncestorIds = new[] { new Guid(parentId) };
+ }
+ else
+ {
+ query.ItemIds = new[] { new Guid(parentId) };
+ }
+ }
+
+ // Studios
+ if (!string.IsNullOrEmpty(studios))
+ {
+ query.StudioIds = studios.Split('|')
+ .Select(i =>
+ {
+ try
+ {
+ return _libraryManager.GetStudio(i);
+ }
+ catch
+ {
+ return null;
+ }
+ }).Where(i => i != null)
+ .Select(i => i!.Id)
+ .ToArray();
+ }
+
+ foreach (var filter in RequestHelpers.GetFilters(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 = new QueryResult<(BaseItem, ItemCounts)>();
+
+ var dtos = result.Items.Select(i =>
+ {
+ var (baseItem, counts) = i;
+ var dto = _dtoService.GetItemByNameDto(baseItem, dtoOptions, null, user);
+
+ if (!string.IsNullOrWhiteSpace(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>
+ {
+ Items = dtos.ToArray(),
+ TotalRecordCount = result.TotalRecordCount
+ };
+ }
+
+ /// <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] string genreName, [FromQuery] Guid? userId)
+ {
+ var dtoOptions = new DtoOptions()
+ .AddClientFields(Request);
+
+ Genre item = new Genre();
+ if (genreName.IndexOf(BaseItem.SlugChar, StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ var result = GetItemFromSlugName<Genre>(_libraryManager, genreName, dtoOptions);
+
+ if (result != null)
+ {
+ item = result;
+ }
+ }
+ else
+ {
+ item = _libraryManager.GetGenre(genreName);
+ }
+
+ if (userId.HasValue && !userId.Equals(Guid.Empty))
+ {
+ 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)
+ where T : BaseItem, new()
+ {
+ var result = libraryManager.GetItemList(new InternalItemsQuery
+ {
+ Name = name.Replace(BaseItem.SlugChar, '&'),
+ IncludeItemTypes = new[] { typeof(T).Name },
+ DtoOptions = dtoOptions
+ }).OfType<T>().FirstOrDefault();
+
+ result ??= libraryManager.GetItemList(new InternalItemsQuery
+ {
+ Name = name.Replace(BaseItem.SlugChar, '/'),
+ IncludeItemTypes = new[] { typeof(T).Name },
+ DtoOptions = dtoOptions
+ }).OfType<T>().FirstOrDefault();
+
+ result ??= libraryManager.GetItemList(new InternalItemsQuery
+ {
+ Name = name.Replace(BaseItem.SlugChar, '?'),
+ IncludeItemTypes = new[] { typeof(T).Name },
+ DtoOptions = dtoOptions
+ }).OfType<T>().FirstOrDefault();
+
+ return result;
+ }
+ }
+}
diff --git a/Jellyfin.Api/Controllers/ImageByNameController.cs b/Jellyfin.Api/Controllers/ImageByNameController.cs
index 4800c0608..5244c35b8 100644
--- a/Jellyfin.Api/Controllers/ImageByNameController.cs
+++ b/Jellyfin.Api/Controllers/ImageByNameController.cs
@@ -64,7 +64,7 @@ namespace Jellyfin.Api.Controllers
[Produces(MediaTypeNames.Application.Octet)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult<FileStreamResult> GetGeneralImage([FromRoute] string name, [FromRoute] string type)
+ public ActionResult<FileStreamResult> GetGeneralImage([FromRoute] string? name, [FromRoute] string? type)
{
var filename = string.Equals(type, "primary", StringComparison.OrdinalIgnoreCase)
? "folder"
@@ -110,8 +110,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<FileStreamResult> GetRatingImage(
- [FromRoute] string theme,
- [FromRoute] string name)
+ [FromRoute] string? theme,
+ [FromRoute] string? name)
{
return GetImageFile(_applicationPaths.RatingsPath, theme, name);
}
@@ -143,8 +143,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<FileStreamResult> GetMediaInfoImage(
- [FromRoute] string theme,
- [FromRoute] string name)
+ [FromRoute] string? theme,
+ [FromRoute] string? name)
{
return GetImageFile(_applicationPaths.MediaInfoImagesPath, theme, name);
}
@@ -156,7 +156,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="theme">Theme to search.</param>
/// <param name="name">File name to search for.</param>
/// <returns>A <see cref="FileStreamResult"/> containing the image contents on success, or a <see cref="NotFoundResult"/> if the image could not be found.</returns>
- private ActionResult<FileStreamResult> GetImageFile(string basePath, string theme, string name)
+ private ActionResult<FileStreamResult> GetImageFile(string basePath, string? theme, string? name)
{
var themeFolder = Path.Combine(basePath, theme);
if (Directory.Exists(themeFolder))
diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs
new file mode 100644
index 000000000..bb980af3e
--- /dev/null
+++ b/Jellyfin.Api/Controllers/InstantMixController.cs
@@ -0,0 +1,328 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Jellyfin.Api.Constants;
+using Jellyfin.Api.Extensions;
+using Jellyfin.Data.Entities;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Playlists;
+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 instant mix controller.
+ /// </summary>
+ [Authorize(Policy = Policies.DefaultAuthorization)]
+ 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="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. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</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("/Songs/{id}/InstantMix")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromSong(
+ [FromRoute] Guid id,
+ [FromQuery] Guid? userId,
+ [FromQuery] int? limit,
+ [FromQuery] string? fields,
+ [FromQuery] bool? enableImages,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] int? imageTypeLimit,
+ [FromQuery] string? enableImageTypes)
+ {
+ var item = _libraryManager.GetItemById(id);
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request)
+ .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 song.
+ /// </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. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</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("/Albums/{id}/InstantMix")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromAlbum(
+ [FromRoute] Guid id,
+ [FromQuery] Guid? userId,
+ [FromQuery] int? limit,
+ [FromQuery] string? fields,
+ [FromQuery] bool? enableImages,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] int? imageTypeLimit,
+ [FromQuery] string? enableImageTypes)
+ {
+ var album = _libraryManager.GetItemById(id);
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request)
+ .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
+ var items = _musicManager.GetInstantMixFromItem(album, user, dtoOptions);
+ return GetResult(items, user, limit, dtoOptions);
+ }
+
+ /// <summary>
+ /// Creates an instant playlist based on a given song.
+ /// </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. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</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("/Playlists/{id}/InstantMix")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromPlaylist(
+ [FromRoute] Guid id,
+ [FromQuery] Guid? userId,
+ [FromQuery] int? limit,
+ [FromQuery] string? fields,
+ [FromQuery] bool? enableImages,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] int? imageTypeLimit,
+ [FromQuery] string? enableImageTypes)
+ {
+ var playlist = (Playlist)_libraryManager.GetItemById(id);
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request)
+ .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
+ var items = _musicManager.GetInstantMixFromItem(playlist, user, dtoOptions);
+ return GetResult(items, user, limit, dtoOptions);
+ }
+
+ /// <summary>
+ /// Creates an instant playlist based on a given song.
+ /// </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. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</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>> GetInstantMixFromMusicGenre(
+ [FromRoute] string? name,
+ [FromQuery] Guid? userId,
+ [FromQuery] int? limit,
+ [FromQuery] string? fields,
+ [FromQuery] bool? enableImages,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] int? imageTypeLimit,
+ [FromQuery] string? enableImageTypes)
+ {
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request)
+ .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 song.
+ /// </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. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</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("/Artists/InstantMix")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists(
+ [FromRoute] Guid id,
+ [FromQuery] Guid? userId,
+ [FromQuery] int? limit,
+ [FromQuery] string? fields,
+ [FromQuery] bool? enableImages,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] int? imageTypeLimit,
+ [FromQuery] string? enableImageTypes)
+ {
+ var item = _libraryManager.GetItemById(id);
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request)
+ .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 song.
+ /// </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. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</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/InstantMix")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenres(
+ [FromRoute] Guid id,
+ [FromQuery] Guid? userId,
+ [FromQuery] int? limit,
+ [FromQuery] string? fields,
+ [FromQuery] bool? enableImages,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] int? imageTypeLimit,
+ [FromQuery] string? enableImageTypes)
+ {
+ var item = _libraryManager.GetItemById(id);
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request)
+ .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 song.
+ /// </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. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</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("/Items/{id}/InstantMix")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromItem(
+ [FromRoute] Guid id,
+ [FromQuery] Guid? userId,
+ [FromQuery] int? limit,
+ [FromQuery] string? fields,
+ [FromQuery] bool? enableImages,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] int? imageTypeLimit,
+ [FromQuery] string? enableImageTypes)
+ {
+ var item = _libraryManager.GetItemById(id);
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request)
+ .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
+ var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
+ return GetResult(items, user, limit, dtoOptions);
+ }
+
+ private QueryResult<BaseItemDto> GetResult(List<BaseItem> items, User? user, int? limit, DtoOptions dtoOptions)
+ {
+ var list = items;
+
+ var result = new QueryResult<BaseItemDto>
+ {
+ TotalRecordCount = list.Count
+ };
+
+ if (limit.HasValue)
+ {
+ list = list.Take(limit.Value).ToList();
+ }
+
+ var returnList = _dtoService.GetBaseItemDtos(list, dtoOptions, user);
+
+ result.Items = returnList;
+
+ return result;
+ }
+ }
+}
diff --git a/Jellyfin.Api/Controllers/ItemRefreshController.cs b/Jellyfin.Api/Controllers/ItemRefreshController.cs
index 3801ce5b7..4697d869d 100644
--- a/Jellyfin.Api/Controllers/ItemRefreshController.cs
+++ b/Jellyfin.Api/Controllers/ItemRefreshController.cs
@@ -1,6 +1,5 @@
using System;
using System.ComponentModel;
-using System.Diagnostics.CodeAnalysis;
using Jellyfin.Api.Constants;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs
index 384f250ec..c9b2aafcc 100644
--- a/Jellyfin.Api/Controllers/ItemUpdateController.cs
+++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs
@@ -193,7 +193,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("/Items/{itemId}/ContentType")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult UpdateItemContentType([FromRoute] Guid itemId, [FromQuery, BindRequired] string contentType)
+ public ActionResult UpdateItemContentType([FromRoute] Guid itemId, [FromQuery, BindRequired] string? contentType)
{
var item = _libraryManager.GetItemById(itemId);
if (item == null)
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
new file mode 100644
index 000000000..41fe47db1
--- /dev/null
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -0,0 +1,592 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using Jellyfin.Api.Constants;
+using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+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>
+ [Authorize(Policy = Policies.DefaultAuthorization)]
+ public class ItemsController : BaseJellyfinApiController
+ {
+ private readonly IUserManager _userManager;
+ private readonly ILibraryManager _libraryManager;
+ private readonly ILocalizationManager _localization;
+ private readonly IDtoService _dtoService;
+ private readonly ILogger _logger;
+
+ /// <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>
+ public ItemsController(
+ IUserManager userManager,
+ ILibraryManager libraryManager,
+ ILocalizationManager localization,
+ IDtoService dtoService,
+ ILogger<ItemsController> logger)
+ {
+ _userManager = userManager;
+ _libraryManager = libraryManager;
+ _localization = localization;
+ _dtoService = dtoService;
+ _logger = logger;
+ }
+
+ /// <summary>
+ /// Gets items based on a query.
+ /// </summary>
+ /// <param name="uId">The user id supplied in the /Users/{uid}/Items.</param>
+ /// <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 delimeted.</param>
+ /// <param name="excludeLocationTypes">Optional. If specified, results will be filtered based on the LocationType. This allows multiple, comma delimeted.</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="excludeItemIds">Optional. If specified, results will be filtered by exxcluding item ids. This allows multiple, comma delimeted.</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 delimeted. 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 delimeted.</param>
+ /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimeted.</param>
+ /// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimeted. 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 delimeted. 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 delimeted.</param>
+ /// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimeted.</param>
+ /// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimeted.</param>
+ /// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimeted.</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 delimeted.</param>
+ /// <param name="artists">Optional. If specified, results will be filtered based on artists. This allows multiple, pipe delimeted.</param>
+ /// <param name="excludeArtistIds">Optional. If specified, results will be filtered based on artist id. This allows multiple, pipe delimeted.</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 delimeted.</param>
+ /// <param name="albumIds">Optional. If specified, results will be filtered based on album id. This allows multiple, pipe delimeted.</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 delimeted.</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 delimeted.</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 delimeted.</param>
+ /// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimeted.</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")]
+ [HttpGet("/Users/{uId}/Items")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult<QueryResult<BaseItemDto>> GetItems(
+ [FromRoute] Guid? uId,
+ [FromQuery] Guid? userId,
+ [FromQuery] string? maxOfficialRating,
+ [FromQuery] bool? hasThemeSong,
+ [FromQuery] bool? hasThemeVideo,
+ [FromQuery] bool? hasSubtitles,
+ [FromQuery] bool? hasSpecialFeature,
+ [FromQuery] bool? hasTrailer,
+ [FromQuery] string? adjacentTo,
+ [FromQuery] int? parentIndexNumber,
+ [FromQuery] bool? hasParentalRating,
+ [FromQuery] bool? isHd,
+ [FromQuery] bool? is4K,
+ [FromQuery] string? locationTypes,
+ [FromQuery] string? 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] string? excludeItemIds,
+ [FromQuery] int? startIndex,
+ [FromQuery] int? limit,
+ [FromQuery] bool? recursive,
+ [FromQuery] string? searchTerm,
+ [FromQuery] string? sortOrder,
+ [FromQuery] string? parentId,
+ [FromQuery] string? fields,
+ [FromQuery] string? excludeItemTypes,
+ [FromQuery] string? includeItemTypes,
+ [FromQuery] string? filters,
+ [FromQuery] bool? isFavorite,
+ [FromQuery] string? mediaTypes,
+ [FromQuery] string? imageTypes,
+ [FromQuery] string? sortBy,
+ [FromQuery] bool? isPlayed,
+ [FromQuery] string? genres,
+ [FromQuery] string? officialRatings,
+ [FromQuery] string? tags,
+ [FromQuery] string? years,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] int? imageTypeLimit,
+ [FromQuery] string? enableImageTypes,
+ [FromQuery] string? person,
+ [FromQuery] string? personIds,
+ [FromQuery] string? personTypes,
+ [FromQuery] string? studios,
+ [FromQuery] string? artists,
+ [FromQuery] string? excludeArtistIds,
+ [FromQuery] string? artistIds,
+ [FromQuery] string? albumArtistIds,
+ [FromQuery] string? contributingArtistIds,
+ [FromQuery] string? albums,
+ [FromQuery] string? albumIds,
+ [FromQuery] string? ids,
+ [FromQuery] string? 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] string? seriesStatus,
+ [FromQuery] string? nameStartsWithOrGreater,
+ [FromQuery] string? nameStartsWith,
+ [FromQuery] string? nameLessThan,
+ [FromQuery] string? studioIds,
+ [FromQuery] string? genreIds,
+ [FromQuery] bool enableTotalRecordCount = true,
+ [FromQuery] bool? enableImages = true)
+ {
+ // use user id route parameter over query parameter
+ userId = uId ?? userId;
+
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request)
+ .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
+
+ if (string.Equals(includeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(includeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase))
+ {
+ parentId = null;
+ }
+
+ BaseItem? item = null;
+ QueryResult<BaseItem> result;
+ if (!string.IsNullOrEmpty(parentId))
+ {
+ item = _libraryManager.GetItemById(parentId);
+ }
+
+ item ??= _libraryManager.GetUserRootFolder();
+
+ if (!(item is Folder folder))
+ {
+ folder = _libraryManager.GetUserRootFolder();
+ }
+
+ if (folder is IHasCollectionType hasCollectionType
+ && string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
+ {
+ recursive = true;
+ includeItemTypes = "Playlist";
+ }
+
+ bool isInEnabledFolder = user!.GetPreference(PreferenceKind.EnabledFolders).Any(i => new Guid(i) == item.Id)
+ // Assume all folders inside an EnabledChannel are enabled
+ || user.GetPreference(PreferenceKind.EnabledChannels).Any(i => new Guid(i) == item.Id);
+
+ var collectionFolders = _libraryManager.GetCollectionFolders(item);
+ foreach (var collectionFolder in collectionFolders)
+ {
+ if (user.GetPreference(PreferenceKind.EnabledFolders).Contains(
+ collectionFolder.Id.ToString("N", CultureInfo.InvariantCulture),
+ StringComparer.OrdinalIgnoreCase))
+ {
+ isInEnabledFolder = true;
+ }
+ }
+
+ if (!(item is UserRootFolder)
+ && !isInEnabledFolder
+ && !user.HasPermission(PermissionKind.EnableAllFolders)
+ && !user.HasPermission(PermissionKind.EnableAllChannels))
+ {
+ _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) || !string.IsNullOrEmpty(ids) || !(item is UserRootFolder))
+ {
+ var query = new InternalItemsQuery(user!)
+ {
+ IsPlayed = isPlayed,
+ MediaTypes = RequestHelpers.Split(mediaTypes, ',', true),
+ IncludeItemTypes = RequestHelpers.Split(includeItemTypes, ',', true),
+ ExcludeItemTypes = RequestHelpers.Split(excludeItemTypes, ',', true),
+ 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,
+ HasOverview = hasOverview,
+ HasOfficialRating = hasOfficialRating,
+ HasParentalRating = hasParentalRating,
+ HasSpecialFeature = hasSpecialFeature,
+ HasSubtitles = hasSubtitles,
+ HasThemeSong = hasThemeSong,
+ HasThemeVideo = hasThemeVideo,
+ HasTrailer = hasTrailer,
+ IsHD = isHd,
+ Is4K = is4K,
+ Tags = RequestHelpers.Split(tags, '|', true),
+ OfficialRatings = RequestHelpers.Split(officialRatings, '|', true),
+ Genres = RequestHelpers.Split(genres, '|', true),
+ ArtistIds = RequestHelpers.GetGuids(artistIds),
+ AlbumArtistIds = RequestHelpers.GetGuids(albumArtistIds),
+ ContributingArtistIds = RequestHelpers.GetGuids(contributingArtistIds),
+ GenreIds = RequestHelpers.GetGuids(genreIds),
+ StudioIds = RequestHelpers.GetGuids(studioIds),
+ Person = person,
+ PersonIds = RequestHelpers.GetGuids(personIds),
+ PersonTypes = RequestHelpers.Split(personTypes, ',', true),
+ Years = RequestHelpers.Split(years, ',', true).Select(int.Parse).ToArray(),
+ ImageTypes = RequestHelpers.Split(imageTypes, ',', true).Select(v => Enum.Parse<ImageType>(v, true)).ToArray(),
+ VideoTypes = RequestHelpers.Split(videoTypes, ',', true).Select(v => Enum.Parse<VideoType>(v, true)).ToArray(),
+ AdjacentTo = adjacentTo,
+ ItemIds = RequestHelpers.GetGuids(ids),
+ MinCommunityRating = minCommunityRating,
+ MinCriticRating = minCriticRating,
+ ParentId = string.IsNullOrWhiteSpace(parentId) ? Guid.Empty : new Guid(parentId),
+ ParentIndexNumber = parentIndexNumber,
+ EnableTotalRecordCount = enableTotalRecordCount,
+ ExcludeItemIds = RequestHelpers.GetGuids(excludeItemIds),
+ DtoOptions = dtoOptions,
+ SearchTerm = searchTerm,
+ MinDateLastSaved = minDateLastSaved?.ToUniversalTime(),
+ MinDateLastSavedForUser = minDateLastSavedForUser?.ToUniversalTime(),
+ MinPremiereDate = minPremiereDate?.ToUniversalTime(),
+ MaxPremiereDate = maxPremiereDate?.ToUniversalTime(),
+ };
+
+ if (!string.IsNullOrWhiteSpace(ids) || !string.IsNullOrWhiteSpace(searchTerm))
+ {
+ query.CollapseBoxSetItems = false;
+ }
+
+ foreach (var filter in RequestHelpers.GetFilters(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 (!string.IsNullOrEmpty(seriesStatus))
+ {
+ query.SeriesStatuses = seriesStatus.Split(',').Select(d => (SeriesStatus)Enum.Parse(typeof(SeriesStatus), d, true)).ToArray();
+ }
+
+ // ExcludeLocationTypes
+ if (!string.IsNullOrEmpty(excludeLocationTypes))
+ {
+ if (excludeLocationTypes.Split(',').Select(d => (LocationType)Enum.Parse(typeof(LocationType), d, true)).ToArray().Contains(LocationType.Virtual))
+ {
+ query.IsVirtualItem = false;
+ }
+ }
+
+ if (!string.IsNullOrEmpty(locationTypes))
+ {
+ var requestedLocationTypes = locationTypes.Split(',');
+ if (requestedLocationTypes.Length > 0 && requestedLocationTypes.Length < 4)
+ {
+ query.IsVirtualItem = requestedLocationTypes.Contains(LocationType.Virtual.ToString());
+ }
+ }
+
+ // Min official rating
+ if (!string.IsNullOrWhiteSpace(minOfficialRating))
+ {
+ query.MinParentalRating = _localization.GetRatingLevel(minOfficialRating);
+ }
+
+ // Max official rating
+ if (!string.IsNullOrWhiteSpace(maxOfficialRating))
+ {
+ query.MaxParentalRating = _localization.GetRatingLevel(maxOfficialRating);
+ }
+
+ // Artists
+ if (!string.IsNullOrEmpty(artists))
+ {
+ query.ArtistIds = artists.Split('|').Select(i =>
+ {
+ try
+ {
+ return _libraryManager.GetArtist(i, new DtoOptions(false));
+ }
+ catch
+ {
+ return null;
+ }
+ }).Where(i => i != null).Select(i => i!.Id).ToArray();
+ }
+
+ // ExcludeArtistIds
+ if (!string.IsNullOrWhiteSpace(excludeArtistIds))
+ {
+ query.ExcludeArtistIds = RequestHelpers.GetGuids(excludeArtistIds);
+ }
+
+ if (!string.IsNullOrWhiteSpace(albumIds))
+ {
+ query.AlbumIds = RequestHelpers.GetGuids(albumIds);
+ }
+
+ // Albums
+ if (!string.IsNullOrEmpty(albums))
+ {
+ query.AlbumIds = albums.Split('|').SelectMany(i =>
+ {
+ return _libraryManager.GetItemIds(new InternalItemsQuery { IncludeItemTypes = new[] { nameof(MusicAlbum) }, Name = i, Limit = 1 });
+ }).ToArray();
+ }
+
+ // Studios
+ if (!string.IsNullOrEmpty(studios))
+ {
+ query.StudioIds = studios.Split('|').Select(i =>
+ {
+ try
+ {
+ return _libraryManager.GetStudio(i);
+ }
+ catch
+ {
+ return null;
+ }
+ }).Where(i => i != 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 && string.Equals(query.IncludeItemTypes[0], "MusicAlbum", StringComparison.OrdinalIgnoreCase))
+ {
+ query.OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.ProductionYear, SortOrder.Descending), new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) };
+ }
+ }
+
+ result = folder.GetItems(query);
+ }
+ else
+ {
+ var itemsArray = folder.GetChildren(user, true);
+ result = new QueryResult<BaseItem> { Items = itemsArray, TotalRecordCount = itemsArray.Count, StartIndex = 0 };
+ }
+
+ return new QueryResult<BaseItemDto> { StartIndex = startIndex.GetValueOrDefault(), TotalRecordCount = result.TotalRecordCount, Items = _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user) };
+ }
+
+ /// <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 delimeted. 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 delimeted.</param>
+ /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimeted.</param>
+ /// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
+ /// <param name="enableImages">Optional. Include image information in output.</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")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult<QueryResult<BaseItemDto>> GetResumeItems(
+ [FromRoute] Guid userId,
+ [FromQuery] int? startIndex,
+ [FromQuery] int? limit,
+ [FromQuery] string? searchTerm,
+ [FromQuery] string? parentId,
+ [FromQuery] string? fields,
+ [FromQuery] string? mediaTypes,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] int? imageTypeLimit,
+ [FromQuery] string? enableImageTypes,
+ [FromQuery] string? excludeItemTypes,
+ [FromQuery] string? includeItemTypes,
+ [FromQuery] bool enableTotalRecordCount = true,
+ [FromQuery] bool? enableImages = true)
+ {
+ var user = _userManager.GetUserById(userId);
+ var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? Guid.Empty : new Guid(parentId);
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request)
+ .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
+
+ var ancestorIds = Array.Empty<Guid>();
+
+ var excludeFolderIds = user.GetPreference(PreferenceKind.LatestItemExcludes);
+ if (parentIdGuid.Equals(Guid.Empty) && excludeFolderIds.Length > 0)
+ {
+ ancestorIds = _libraryManager.GetUserRootFolder().GetChildren(user, true)
+ .Where(i => i is Folder)
+ .Where(i => !excludeFolderIds.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture)))
+ .Select(i => i.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 = RequestHelpers.Split(mediaTypes, ',', true),
+ IsVirtualItem = false,
+ CollapseBoxSetItems = false,
+ EnableTotalRecordCount = enableTotalRecordCount,
+ AncestorIds = ancestorIds,
+ IncludeItemTypes = RequestHelpers.Split(includeItemTypes, ',', true),
+ ExcludeItemTypes = RequestHelpers.Split(excludeItemTypes, ',', true),
+ SearchTerm = searchTerm
+ });
+
+ var returnItems = _dtoService.GetBaseItemDtos(itemsResult.Items, dtoOptions, user);
+
+ return new QueryResult<BaseItemDto>
+ {
+ StartIndex = startIndex.GetValueOrDefault(),
+ TotalRecordCount = itemsResult.TotalRecordCount,
+ Items = returnItems
+ };
+ }
+ }
+}
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index 1ecf2ac73..5ad466c55 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -146,11 +145,11 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<ThemeMediaResult> GetThemeSongs(
[FromRoute] Guid itemId,
- [FromQuery] Guid userId,
- [FromQuery] bool inheritFromParent)
+ [FromQuery] Guid? userId,
+ [FromQuery] bool inheritFromParent = false)
{
- var user = !userId.Equals(Guid.Empty)
- ? _userManager.GetUserById(userId)
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
: null;
var item = itemId.Equals(Guid.Empty)
@@ -212,11 +211,11 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<ThemeMediaResult> GetThemeVideos(
[FromRoute] Guid itemId,
- [FromQuery] Guid userId,
- [FromQuery] bool inheritFromParent)
+ [FromQuery] Guid? userId,
+ [FromQuery] bool inheritFromParent = false)
{
- var user = !userId.Equals(Guid.Empty)
- ? _userManager.GetUserById(userId)
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
: null;
var item = itemId.Equals(Guid.Empty)
@@ -277,8 +276,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<AllThemeMediaResult> GetThemeMedia(
[FromRoute] Guid itemId,
- [FromQuery] Guid userId,
- [FromQuery] bool inheritFromParent)
+ [FromQuery] Guid? userId,
+ [FromQuery] bool inheritFromParent = false)
{
var themeSongs = GetThemeSongs(
itemId,
@@ -361,12 +360,14 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
- public ActionResult DeleteItems([FromQuery] string ids)
+ public ActionResult DeleteItems([FromQuery] string? ids)
{
- var itemIds = string.IsNullOrWhiteSpace(ids)
- ? Array.Empty<string>()
- : RequestHelpers.Split(ids, ',', true);
+ if (string.IsNullOrEmpty(ids))
+ {
+ return NoContent();
+ }
+ var itemIds = RequestHelpers.Split(ids, ',', true);
foreach (var i in itemIds)
{
var item = _libraryManager.GetItemById(i);
@@ -403,12 +404,12 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<ItemCounts> GetItemCounts(
- [FromQuery] Guid userId,
+ [FromQuery] Guid? userId,
[FromQuery] bool? isFavorite)
{
- var user = userId.Equals(Guid.Empty)
- ? null
- : _userManager.GetUserById(userId);
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
var counts = new ItemCounts
{
@@ -437,7 +438,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult<IEnumerable<BaseItemDto>> GetAncestors([FromRoute] Guid itemId, [FromQuery] Guid userId)
+ public ActionResult<IEnumerable<BaseItemDto>> GetAncestors([FromRoute] Guid itemId, [FromQuery] Guid? userId)
{
var item = _libraryManager.GetItemById(itemId);
@@ -448,8 +449,8 @@ namespace Jellyfin.Api.Controllers
var baseItemDtos = new List<BaseItemDto>();
- var user = !userId.Equals(Guid.Empty)
- ? _userManager.GetUserById(userId)
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
: null;
var dtoOptions = new DtoOptions().AddClientFields(Request);
@@ -524,7 +525,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("/Library/Series/Updated")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult PostUpdatedSeries([FromQuery] string tvdbId)
+ public ActionResult PostUpdatedSeries([FromQuery] string? tvdbId)
{
var series = _libraryManager.GetItemList(new InternalItemsQuery
{
@@ -554,7 +555,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("/Library/Movies/Updated")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult PostUpdatedMovies([FromRoute] string tmdbId, [FromRoute] string imdbId)
+ public ActionResult PostUpdatedMovies([FromRoute] string? tmdbId, [FromRoute] string? imdbId)
{
var movies = _libraryManager.GetItemList(new InternalItemsQuery
{
@@ -687,10 +688,10 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
[FromRoute] Guid itemId,
- [FromQuery] string excludeArtistIds,
- [FromQuery] Guid userId,
+ [FromQuery] string? excludeArtistIds,
+ [FromQuery] Guid? userId,
[FromQuery] int? limit,
- [FromQuery] string fields)
+ [FromQuery] string? fields)
{
var item = itemId.Equals(Guid.Empty)
? (!userId.Equals(Guid.Empty)
@@ -737,7 +738,9 @@ namespace Jellyfin.Api.Controllers
[HttpGet("/Libraries/AvailableOptions")]
[Authorize(Policy = Policies.FirstTimeSetupOrElevated)]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<LibraryOptionsResultDto> GetLibraryOptionsInfo([FromQuery] string libraryContentType, [FromQuery] bool isNewLibrary)
+ public ActionResult<LibraryOptionsResultDto> GetLibraryOptionsInfo(
+ [FromQuery] string? libraryContentType,
+ [FromQuery] bool isNewLibrary = false)
{
var result = new LibraryOptionsResultDto();
@@ -877,14 +880,16 @@ namespace Jellyfin.Api.Controllers
private QueryResult<BaseItemDto> GetSimilarItemsResult(
BaseItem item,
- string excludeArtistIds,
- Guid userId,
+ string? excludeArtistIds,
+ Guid? userId,
int? limit,
- string fields,
+ string? fields,
string[] includeItemTypes,
bool isMovie)
{
- var user = !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId) : null;
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
var dtoOptions = new DtoOptions()
.AddItemFields(fields)
.AddClientFields(Request);
@@ -942,7 +947,7 @@ namespace Jellyfin.Api.Controllers
return result;
}
- private static string[] GetRepresentativeItemTypes(string contentType)
+ private static string[] GetRepresentativeItemTypes(string? contentType)
{
return contentType switch
{
diff --git a/Jellyfin.Api/Controllers/LibraryStructureController.cs b/Jellyfin.Api/Controllers/LibraryStructureController.cs
index e4ac019c9..d3537a7a7 100644
--- a/Jellyfin.Api/Controllers/LibraryStructureController.cs
+++ b/Jellyfin.Api/Controllers/LibraryStructureController.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -64,19 +63,19 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <param name="name">The name of the virtual folder.</param>
/// <param name="collectionType">The type of the collection.</param>
- /// <param name="refreshLibrary">Whether to refresh the library.</param>
/// <param name="paths">The paths of the virtual folder.</param>
/// <param name="libraryOptions">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] string collectionType,
- [FromQuery] bool refreshLibrary,
+ [FromQuery] string? name,
+ [FromQuery] string? collectionType,
[FromQuery] string[] paths,
- [FromQuery] LibraryOptions libraryOptions)
+ [FromQuery] LibraryOptions? libraryOptions,
+ [FromQuery] bool refreshLibrary = false)
{
libraryOptions ??= new LibraryOptions();
@@ -100,8 +99,8 @@ namespace Jellyfin.Api.Controllers
[HttpDelete]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> RemoveVirtualFolder(
- [FromQuery] string name,
- [FromQuery] bool refreshLibrary)
+ [FromQuery] string? name,
+ [FromQuery] bool refreshLibrary = false)
{
await _libraryManager.RemoveVirtualFolder(name, refreshLibrary).ConfigureAwait(false);
return NoContent();
@@ -123,9 +122,9 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public ActionResult RenameVirtualFolder(
- [FromQuery] string name,
- [FromQuery] string newName,
- [FromQuery] bool refreshLibrary)
+ [FromQuery] string? name,
+ [FromQuery] string? newName,
+ [FromQuery] bool refreshLibrary = false)
{
if (string.IsNullOrWhiteSpace(name))
{
@@ -205,10 +204,10 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Paths")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult AddMediaPath(
- [FromQuery] string name,
- [FromQuery] string path,
- [FromQuery] MediaPathInfo pathInfo,
- [FromQuery] bool refreshLibrary)
+ [FromQuery] string? name,
+ [FromQuery] string? path,
+ [FromQuery] MediaPathInfo? pathInfo,
+ [FromQuery] bool refreshLibrary = false)
{
if (string.IsNullOrWhiteSpace(name))
{
@@ -256,8 +255,8 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Paths/Update")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult UpdateMediaPath(
- [FromQuery] string name,
- [FromQuery] MediaPathInfo pathInfo)
+ [FromQuery] string? name,
+ [FromQuery] MediaPathInfo? pathInfo)
{
if (string.IsNullOrWhiteSpace(name))
{
@@ -280,9 +279,9 @@ namespace Jellyfin.Api.Controllers
[HttpDelete("Paths")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult RemoveMediaPath(
- [FromQuery] string name,
- [FromQuery] string path,
- [FromQuery] bool refreshLibrary)
+ [FromQuery] string? name,
+ [FromQuery] string? path,
+ [FromQuery] bool refreshLibrary = false)
{
if (string.IsNullOrWhiteSpace(name))
{
@@ -327,8 +326,8 @@ namespace Jellyfin.Api.Controllers
[HttpPost("LibraryOptions")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult UpdateLibraryOptions(
- [FromQuery] string id,
- [FromQuery] LibraryOptions libraryOptions)
+ [FromQuery] string? id,
+ [FromQuery] LibraryOptions? libraryOptions)
{
var collectionFolder = (CollectionFolder)_libraryManager.GetItemById(id);
diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
new file mode 100644
index 000000000..bc5446510
--- /dev/null
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -0,0 +1,1236 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+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.Constants;
+using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
+using Jellyfin.Api.Models.LiveTvDtos;
+using Jellyfin.Data.Enums;
+using MediaBrowser.Common;
+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.Net;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
+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 IUserManager _userManager;
+ private readonly IHttpClient _httpClient;
+ private readonly ILibraryManager _libraryManager;
+ private readonly IDtoService _dtoService;
+ private readonly ISessionContext _sessionContext;
+ private readonly IStreamHelper _streamHelper;
+ private readonly IMediaSourceManager _mediaSourceManager;
+ private readonly IConfigurationManager _configurationManager;
+
+ /// <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="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
+ /// <param name="httpClient">Instance of the <see cref="IHttpClient"/> 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="sessionContext">Instance of the <see cref="ISessionContext"/> interface.</param>
+ /// <param name="streamHelper">Instance of the <see cref="IStreamHelper"/> interface.</param>
+ /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
+ /// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
+ public LiveTvController(
+ ILiveTvManager liveTvManager,
+ IUserManager userManager,
+ IHttpClient httpClient,
+ ILibraryManager libraryManager,
+ IDtoService dtoService,
+ ISessionContext sessionContext,
+ IStreamHelper streamHelper,
+ IMediaSourceManager mediaSourceManager,
+ IConfigurationManager configurationManager)
+ {
+ _liveTvManager = liveTvManager;
+ _userManager = userManager;
+ _httpClient = httpClient;
+ _libraryManager = libraryManager;
+ _dtoService = dtoService;
+ _sessionContext = sessionContext;
+ _streamHelper = streamHelper;
+ _mediaSourceManager = mediaSourceManager;
+ _configurationManager = configurationManager;
+ }
+
+ /// <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.DefaultAuthorization)]
+ 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. 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="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.DefaultAuthorization)]
+ public ActionResult<QueryResult<BaseItemDto>> GetChannels(
+ [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] string? enableImageTypes,
+ [FromQuery] string? fields,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] string? sortBy,
+ [FromQuery] SortOrder? sortOrder,
+ [FromQuery] bool enableFavoriteSorting = false,
+ [FromQuery] bool addCurrentProgram = true)
+ {
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request)
+ .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
+
+ var channelResult = _liveTvManager.GetInternalChannels(
+ new LiveTvChannelQuery
+ {
+ ChannelType = type,
+ UserId = userId ?? Guid.Empty,
+ StartIndex = startIndex,
+ Limit = limit,
+ IsFavorite = isFavorite,
+ IsLiked = isLiked,
+ IsDisliked = isDisliked,
+ EnableFavoriteSorting = enableFavoriteSorting,
+ IsMovie = isMovie,
+ IsSeries = isSeries,
+ IsNews = isNews,
+ IsKids = isKids,
+ IsSports = isSports,
+ SortBy = RequestHelpers.Split(sortBy, ',', true),
+ SortOrder = sortOrder ?? SortOrder.Ascending,
+ AddCurrentProgram = addCurrentProgram
+ },
+ dtoOptions,
+ CancellationToken.None);
+
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+
+ 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>
+ {
+ Items = returnArray,
+ TotalRecordCount = channelResult.TotalRecordCount
+ };
+ }
+
+ /// <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>
+ /// <returns>An <see cref="OkResult"/> containing the live tv channel.</returns>
+ [HttpGet("Channels/{channelId}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
+ public ActionResult<BaseItemDto> GetChannel([FromRoute] Guid channelId, [FromQuery] Guid? userId)
+ {
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+ var item = channelId.Equals(Guid.Empty)
+ ? _libraryManager.GetUserRootFolder()
+ : _libraryManager.GetItemById(channelId);
+
+ var dtoOptions = new DtoOptions()
+ .AddClientFields(Request);
+ 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. 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="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.DefaultAuthorization)]
+ public 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] string? enableImageTypes,
+ [FromQuery] string? 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)
+ {
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request)
+ .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
+
+ return _liveTvManager.GetRecordings(
+ new RecordingQuery
+ {
+ ChannelId = channelId,
+ UserId = userId ?? Guid.Empty,
+ 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 = RequestHelpers.GetItemFields(fields),
+ ImageTypeLimit = imageTypeLimit,
+ EnableImages = enableImages
+ }, dtoOptions);
+ }
+
+ /// <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. 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="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.DefaultAuthorization)]
+ [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] string? enableImageTypes,
+ [FromQuery] string? 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.DefaultAuthorization)]
+ [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.DefaultAuthorization)]
+ public ActionResult<QueryResult<BaseItemDto>> GetRecordingFolders([FromQuery] Guid? userId)
+ {
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+ var folders = _liveTvManager.GetRecordingFolders(user);
+
+ var returnArray = _dtoService.GetBaseItemDtos(folders, new DtoOptions(), user);
+
+ return new QueryResult<BaseItemDto>
+ {
+ Items = returnArray,
+ TotalRecordCount = returnArray.Count
+ };
+ }
+
+ /// <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>
+ /// <returns>An <see cref="OkResult"/> containing the live tv recording.</returns>
+ [HttpGet("Recordings/{recordingId}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
+ public ActionResult<BaseItemDto> GetRecording([FromRoute] Guid recordingId, [FromQuery] Guid? userId)
+ {
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+ var item = recordingId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(recordingId);
+
+ var dtoOptions = new DtoOptions()
+ .AddClientFields(Request);
+
+ 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.DefaultAuthorization)]
+ public ActionResult ResetTuner([FromRoute] string tunerId)
+ {
+ AssertUserCanManageLiveTv();
+ _liveTvManager.ResetTuner(tunerId, CancellationToken.None);
+ 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.DefaultAuthorization)]
+ public async Task<ActionResult<TimerInfoDto>> GetTimer(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.DefaultAuthorization)]
+ 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.DefaultAuthorization)]
+ 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. 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="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.DefaultAuthorization)]
+ public async Task<ActionResult<QueryResult<BaseItemDto>>> GetPrograms(
+ [FromQuery] string? 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] string? sortBy,
+ [FromQuery] string? sortOrder,
+ [FromQuery] string? genres,
+ [FromQuery] string? genreIds,
+ [FromQuery] bool? enableImages,
+ [FromQuery] int? imageTypeLimit,
+ [FromQuery] string? enableImageTypes,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] string? seriesTimerId,
+ [FromQuery] Guid? librarySeriesId,
+ [FromQuery] string? fields,
+ [FromQuery] bool enableTotalRecordCount = true)
+ {
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+
+ var query = new InternalItemsQuery(user)
+ {
+ ChannelIds = RequestHelpers.Split(channelIds, ',', true)
+ .Select(i => new Guid(i)).ToArray(),
+ 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 = RequestHelpers.Split(genres, ',', true),
+ GenreIds = RequestHelpers.GetGuids(genreIds)
+ };
+
+ if (!librarySeriesId.Equals(Guid.Empty))
+ {
+ query.IsSeries = true;
+
+ if (_libraryManager.GetItemById(librarySeriesId ?? Guid.Empty) is Series series)
+ {
+ query.Name = series.Name;
+ }
+ }
+
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request)
+ .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.DefaultAuthorization)]
+ public async Task<ActionResult<QueryResult<BaseItemDto>>> GetPrograms([FromBody] GetProgramsDto body)
+ {
+ var user = body.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(body.UserId);
+
+ var query = new InternalItemsQuery(user)
+ {
+ ChannelIds = RequestHelpers.Split(body.ChannelIds, ',', true)
+ .Select(i => new Guid(i)).ToArray(),
+ 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 = RequestHelpers.Split(body.Genres, ',', true),
+ GenreIds = RequestHelpers.GetGuids(body.GenreIds)
+ };
+
+ if (!body.LibrarySeriesId.Equals(Guid.Empty))
+ {
+ query.IsSeries = true;
+
+ if (_libraryManager.GetItemById(body.LibrarySeriesId) is Series series)
+ {
+ query.Name = series.Name;
+ }
+ }
+
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(body.Fields)
+ .AddClientFields(Request)
+ .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="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. 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="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.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult<QueryResult<BaseItemDto>> GetRecommendedPrograms(
+ [FromQuery] Guid? userId,
+ [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] string? enableImageTypes,
+ [FromQuery] string? genreIds,
+ [FromQuery] string? fields,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] bool enableTotalRecordCount = true)
+ {
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+
+ var query = new InternalItemsQuery(user)
+ {
+ IsAiring = isAiring,
+ Limit = limit,
+ HasAired = hasAired,
+ IsSeries = isSeries,
+ IsMovie = isMovie,
+ IsKids = isKids,
+ IsNews = isNews,
+ IsSports = isSports,
+ EnableTotalRecordCount = enableTotalRecordCount,
+ GenreIds = RequestHelpers.GetGuids(genreIds)
+ };
+
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request)
+ .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
+ return _liveTvManager.GetRecommendedPrograms(query, dtoOptions, CancellationToken.None);
+ }
+
+ /// <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.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task<ActionResult<BaseItemDto>> GetProgram(
+ [FromRoute] string programId,
+ [FromQuery] Guid? userId)
+ {
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+
+ 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.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public ActionResult DeleteRecording([FromRoute] Guid recordingId)
+ {
+ AssertUserCanManageLiveTv();
+
+ var item = _libraryManager.GetItemById(recordingId);
+ if (item == 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.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ public async Task<ActionResult> CancelTimer([FromRoute] string timerId)
+ {
+ AssertUserCanManageLiveTv();
+ 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.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")]
+ public async Task<ActionResult> UpdateTimer([FromRoute] string timerId, [FromBody] TimerInfoDto timerInfo)
+ {
+ AssertUserCanManageLiveTv();
+ 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.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ public async Task<ActionResult> CreateTimer([FromBody] TimerInfoDto timerInfo)
+ {
+ AssertUserCanManageLiveTv();
+ 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.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task<ActionResult<SeriesTimerInfoDto>> GetSeriesTimer([FromRoute] string timerId)
+ {
+ var timer = await _liveTvManager.GetSeriesTimer(timerId, CancellationToken.None).ConfigureAwait(false);
+ if (timer == 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.DefaultAuthorization)]
+ [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.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ public async Task<ActionResult> CancelSeriesTimer([FromRoute] string timerId)
+ {
+ AssertUserCanManageLiveTv();
+ 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.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")]
+ public async Task<ActionResult> UpdateSeriesTimer([FromRoute] string timerId, [FromBody] SeriesTimerInfoDto seriesTimerInfo)
+ {
+ AssertUserCanManageLiveTv();
+ 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.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ public async Task<ActionResult> CreateSeriesTimer([FromBody] SeriesTimerInfoDto seriesTimerInfo)
+ {
+ AssertUserCanManageLiveTv();
+ 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.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [Obsolete("This endpoint is obsolete.")]
+ public ActionResult<BaseItemDto> GetRecordingGroup([FromQuery] Guid? groupId)
+ {
+ return NotFound();
+ }
+
+ /// <summary>
+ /// Get guid info.
+ /// </summary>
+ /// <response code="200">Guid info returned.</response>
+ /// <returns>An <see cref="OkResult"/> containing the guide info.</returns>
+ [HttpGet("GuideInfo")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult<GuideInfo> GetGuideInfo()
+ {
+ return _liveTvManager.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.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task<ActionResult<TunerHostInfo>> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo)
+ {
+ return await _liveTvManager.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.DefaultAuthorization)]
+ [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.DefaultAuthorization)]
+ [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>
+ [HttpGet("ListingProviders")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
+ [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)
+ {
+ using var sha = SHA1.Create();
+ if (!string.IsNullOrEmpty(pw))
+ {
+ listingsProviderInfo.Password = Hex.Encode(sha.ComputeHash(Encoding.UTF8.GetBytes(pw)));
+ }
+
+ return await _liveTvManager.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.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ public ActionResult DeleteListingProvider([FromQuery] string? id)
+ {
+ _liveTvManager.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.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task<ActionResult<IEnumerable<NameIdPair>>> GetLineups(
+ [FromQuery] string? id,
+ [FromQuery] string? type,
+ [FromQuery] string? location,
+ [FromQuery] string? country)
+ {
+ return await _liveTvManager.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.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task<ActionResult> GetSchedulesDirectCountries()
+ {
+ // https://json.schedulesdirect.org/20141201/available/countries
+ var response = await _httpClient.Get(new HttpRequestOptions
+ {
+ Url = "https://json.schedulesdirect.org/20141201/available/countries",
+ BufferContent = false
+ }).ConfigureAwait(false);
+ return File(response, 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.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task<ActionResult<ChannelMappingOptionsDto>> GetChannelMappingOptions([FromQuery] string? providerId)
+ {
+ var config = _configurationManager.GetConfiguration<LiveTvOptions>("livetv");
+
+ var listingsProviderInfo = config.ListingProviders.First(i => string.Equals(providerId, i.Id, StringComparison.OrdinalIgnoreCase));
+
+ var listingsProviderName = _liveTvManager.ListingProviders.First(i => string.Equals(i.Type, listingsProviderInfo.Type, StringComparison.OrdinalIgnoreCase)).Name;
+
+ var tunerChannels = await _liveTvManager.GetChannelsForListingsProvider(providerId, CancellationToken.None)
+ .ConfigureAwait(false);
+
+ var providerChannels = await _liveTvManager.GetChannelsFromListingsProviderData(providerId, CancellationToken.None)
+ .ConfigureAwait(false);
+
+ var mappings = listingsProviderInfo.ChannelMappings;
+
+ return new ChannelMappingOptionsDto
+ {
+ TunerChannels = tunerChannels.Select(i => _liveTvManager.GetTunerChannelMapping(i, mappings, providerChannels)).ToList(),
+ ProviderChannels = providerChannels.Select(i => new NameIdPair
+ {
+ Name = i.Name,
+ Id = i.Id
+ }).ToList(),
+ Mappings = mappings,
+ ProviderName = listingsProviderName
+ };
+ }
+
+ /// <summary>
+ /// Set channel mappings.
+ /// </summary>
+ /// <param name="providerId">Provider id.</param>
+ /// <param name="tunerChannelId">Tuner channel id.</param>
+ /// <param name="providerChannelId">Provider channel id.</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.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task<ActionResult<TunerChannelMapping>> SetChannelMapping(
+ [FromQuery] string? providerId,
+ [FromQuery] string? tunerChannelId,
+ [FromQuery] string? providerChannelId)
+ {
+ return await _liveTvManager.SetChannelMapping(providerId, tunerChannelId, providerChannelId).ConfigureAwait(false);
+ }
+
+ /// <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.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult<IEnumerable<NameIdPair>> GetTunerHostTypes()
+ {
+ return _liveTvManager.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")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task<ActionResult<IEnumerable<TunerHostInfo>>> DiscoverTuners([FromQuery] bool newDevicesOnly = false)
+ {
+ return await _liveTvManager.DiscoverTuners(newDevicesOnly, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ /// <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)]
+ public async Task<ActionResult> GetLiveRecordingFile([FromRoute] string recordingId)
+ {
+ var path = _liveTvManager.GetEmbyTvActiveRecordingPath(recordingId);
+
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ return NotFound();
+ }
+
+ await using var memoryStream = new MemoryStream();
+ await new ProgressiveFileCopier(_streamHelper, path).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false);
+ return File(memoryStream, 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)]
+ public async Task<ActionResult> GetLiveStreamFile([FromRoute] string streamId, [FromRoute] string container)
+ {
+ var liveStreamInfo = await _mediaSourceManager.GetDirectStreamProviderByUniqueId(streamId, CancellationToken.None).ConfigureAwait(false);
+ if (liveStreamInfo == null)
+ {
+ return NotFound();
+ }
+
+ await using var memoryStream = new MemoryStream();
+ await new ProgressiveFileCopier(_streamHelper, liveStreamInfo).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false);
+ return File(memoryStream, MimeTypes.GetMimeType("file." + container));
+ }
+
+ private void AssertUserCanManageLiveTv()
+ {
+ var user = _sessionContext.GetUser(Request);
+
+ if (user == null)
+ {
+ throw new SecurityException("Anonymous live tv management is not allowed.");
+ }
+
+ if (!user.HasPermission(PermissionKind.EnableLiveTvManagement))
+ {
+ throw new SecurityException("The current user does not have permission to manage live tv.");
+ }
+ }
+ }
+}
diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs
new file mode 100644
index 000000000..da400f510
--- /dev/null
+++ b/Jellyfin.Api/Controllers/MediaInfoController.cs
@@ -0,0 +1,775 @@
+using System;
+using System.Buffers;
+using System.Globalization;
+using System.Linq;
+using System.Net.Mime;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Jellyfin.Api.Constants;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
+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.Controller.Net;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+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 media info controller.
+ /// </summary>
+ [Authorize(Policy = Policies.DefaultAuthorization)]
+ public class MediaInfoController : BaseJellyfinApiController
+ {
+ private readonly IMediaSourceManager _mediaSourceManager;
+ private readonly IDeviceManager _deviceManager;
+ private readonly ILibraryManager _libraryManager;
+ private readonly INetworkManager _networkManager;
+ private readonly IMediaEncoder _mediaEncoder;
+ private readonly IUserManager _userManager;
+ private readonly IAuthorizationContext _authContext;
+ private readonly ILogger _logger;
+ private readonly IServerConfigurationManager _serverConfigurationManager;
+
+ /// <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="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
+ /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
+ /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
+ /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
+ /// <param name="logger">Instance of the <see cref="ILogger{MediaInfoController}"/> interface.</param>
+ /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
+ public MediaInfoController(
+ IMediaSourceManager mediaSourceManager,
+ IDeviceManager deviceManager,
+ ILibraryManager libraryManager,
+ INetworkManager networkManager,
+ IMediaEncoder mediaEncoder,
+ IUserManager userManager,
+ IAuthorizationContext authContext,
+ ILogger<MediaInfoController> logger,
+ IServerConfigurationManager serverConfigurationManager)
+ {
+ _mediaSourceManager = mediaSourceManager;
+ _deviceManager = deviceManager;
+ _libraryManager = libraryManager;
+ _networkManager = networkManager;
+ _mediaEncoder = mediaEncoder;
+ _userManager = userManager;
+ _authContext = authContext;
+ _logger = logger;
+ _serverConfigurationManager = serverConfigurationManager;
+ }
+
+ /// <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>
+ /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback information.</returns>
+ [HttpGet("/Items/{itemId}/PlaybackInfo")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute] Guid itemId, [FromQuery] Guid? userId)
+ {
+ return await GetPlaybackInfoInternal(itemId, userId, null, null).ConfigureAwait(false);
+ }
+
+ /// <summary>
+ /// Gets live playback media info for an item.
+ /// </summary>
+ /// <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="deviceProfile">The device profile.</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>
+ /// <response code="200">Playback info returned.</response>
+ /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback info.</returns>
+ [HttpPost("/Items/{itemId}/PlaybackInfo")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo(
+ [FromRoute] Guid itemId,
+ [FromQuery] Guid? userId,
+ [FromQuery] long? maxStreamingBitrate,
+ [FromQuery] long? startTimeTicks,
+ [FromQuery] int? audioStreamIndex,
+ [FromQuery] int? subtitleStreamIndex,
+ [FromQuery] int? maxAudioChannels,
+ [FromQuery] string? mediaSourceId,
+ [FromQuery] string? liveStreamId,
+ [FromQuery] DeviceProfile? deviceProfile,
+ [FromQuery] bool autoOpenLiveStream = false,
+ [FromQuery] bool enableDirectPlay = true,
+ [FromQuery] bool enableDirectStream = true,
+ [FromQuery] bool enableTranscoding = true,
+ [FromQuery] bool allowVideoStreamCopy = true,
+ [FromQuery] bool allowAudioStreamCopy = true)
+ {
+ var authInfo = _authContext.GetAuthorizationInfo(Request);
+
+ var profile = deviceProfile;
+
+ _logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", profile);
+
+ if (profile == null)
+ {
+ var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
+ if (caps != null)
+ {
+ profile = caps.DeviceProfile;
+ }
+ }
+
+ var info = await GetPlaybackInfoInternal(itemId, userId, mediaSourceId, liveStreamId).ConfigureAwait(false);
+
+ if (profile != null)
+ {
+ // set device specific data
+ var item = _libraryManager.GetItemById(itemId);
+
+ foreach (var mediaSource in info.MediaSources)
+ {
+ SetDeviceSpecificData(
+ item,
+ mediaSource,
+ profile,
+ authInfo,
+ maxStreamingBitrate ?? profile.MaxStreamingBitrate,
+ startTimeTicks ?? 0,
+ mediaSourceId ?? string.Empty,
+ audioStreamIndex,
+ subtitleStreamIndex,
+ maxAudioChannels,
+ info!.PlaySessionId!,
+ userId ?? Guid.Empty,
+ enableDirectPlay,
+ enableDirectStream,
+ enableTranscoding,
+ allowVideoStreamCopy,
+ allowAudioStreamCopy);
+ }
+
+ SortMediaSources(info, maxStreamingBitrate);
+ }
+
+ if (autoOpenLiveStream)
+ {
+ var mediaSource = string.IsNullOrWhiteSpace(mediaSourceId) ? info.MediaSources[0] : info.MediaSources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.Ordinal));
+
+ if (mediaSource != null && mediaSource.RequiresOpening && string.IsNullOrWhiteSpace(mediaSource.LiveStreamId))
+ {
+ var openStreamResult = await OpenMediaSource(new LiveStreamRequest
+ {
+ AudioStreamIndex = audioStreamIndex,
+ DeviceProfile = deviceProfile,
+ EnableDirectPlay = enableDirectPlay,
+ EnableDirectStream = enableDirectStream,
+ ItemId = itemId,
+ MaxAudioChannels = maxAudioChannels,
+ MaxStreamingBitrate = maxStreamingBitrate,
+ PlaySessionId = info.PlaySessionId,
+ StartTimeTicks = startTimeTicks,
+ SubtitleStreamIndex = subtitleStreamIndex,
+ UserId = userId ?? Guid.Empty,
+ OpenToken = mediaSource.OpenToken
+ }).ConfigureAwait(false);
+
+ info.MediaSources = new[] { openStreamResult.MediaSource };
+ }
+ }
+
+ if (info.MediaSources != null)
+ {
+ foreach (var mediaSource in info.MediaSources)
+ {
+ NormalizeMediaSourceContainer(mediaSource, profile!, DlnaProfileType.Video);
+ }
+ }
+
+ 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="deviceProfile">The device profile.</param>
+ /// <param name="directPlayProtocols">The direct play protocols. Default: <see cref="MediaProtocol.Http"/>.</param>
+ /// <param name="enableDirectPlay">Whether to enable direct play. Default: true.</param>
+ /// <param name="enableDirectStream">Whether to enable direct stream. Default: true.</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] long? maxStreamingBitrate,
+ [FromQuery] long? startTimeTicks,
+ [FromQuery] int? audioStreamIndex,
+ [FromQuery] int? subtitleStreamIndex,
+ [FromQuery] int? maxAudioChannels,
+ [FromQuery] Guid? itemId,
+ [FromQuery] DeviceProfile? deviceProfile,
+ [FromQuery] MediaProtocol[] directPlayProtocols,
+ [FromQuery] bool enableDirectPlay = true,
+ [FromQuery] bool enableDirectStream = true)
+ {
+ var request = new LiveStreamRequest
+ {
+ OpenToken = openToken,
+ UserId = userId ?? Guid.Empty,
+ PlaySessionId = playSessionId,
+ MaxStreamingBitrate = maxStreamingBitrate,
+ StartTimeTicks = startTimeTicks,
+ AudioStreamIndex = audioStreamIndex,
+ SubtitleStreamIndex = subtitleStreamIndex,
+ MaxAudioChannels = maxAudioChannels,
+ ItemId = itemId ?? Guid.Empty,
+ DeviceProfile = deviceProfile,
+ EnableDirectPlay = enableDirectPlay,
+ EnableDirectStream = enableDirectStream,
+ DirectPlayProtocols = directPlayProtocols ?? new[] { MediaProtocol.Http }
+ };
+ return await OpenMediaSource(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 ActionResult CloseLiveStream([FromQuery] string? liveStreamId)
+ {
+ _mediaSourceManager.CloseLiveStream(liveStreamId).GetAwaiter().GetResult();
+ 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>
+ /// <response code="400">Size has to be a numer between 0 and 10,000,000.</response>
+ /// <returns>A <see cref="FileResult"/> with specified bitrate.</returns>
+ [HttpGet("/Playback/BitrateTest")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ [Produces(MediaTypeNames.Application.Octet)]
+ public ActionResult GetBitrateTestBytes([FromQuery] int size = 102400)
+ {
+ const int MaxSize = 10_000_000;
+
+ if (size <= 0)
+ {
+ return BadRequest($"The requested size ({size}) is equal to or smaller than 0.");
+ }
+
+ if (size > MaxSize)
+ {
+ return BadRequest($"The requested size ({size}) is larger than the max allowed value ({MaxSize}).");
+ }
+
+ byte[] buffer = ArrayPool<byte>.Shared.Rent(size);
+ try
+ {
+ new Random().NextBytes(buffer);
+ return File(buffer, MediaTypeNames.Application.Octet);
+ }
+ finally
+ {
+ ArrayPool<byte>.Shared.Return(buffer);
+ }
+ }
+
+ private async Task<PlaybackInfoResponse> GetPlaybackInfoInternal(
+ Guid id,
+ Guid? userId,
+ string? mediaSourceId = null,
+ string? liveStreamId = null)
+ {
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+ var item = _libraryManager.GetItemById(id);
+ 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?
+ result.MediaSources = JsonSerializer.Deserialize<MediaSourceInfo[]>(JsonSerializer.SerializeToUtf8Bytes(mediaSources));
+
+ result.PlaySessionId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
+ }
+
+ return result;
+ }
+
+ private void NormalizeMediaSourceContainer(MediaSourceInfo mediaSource, DeviceProfile profile, DlnaProfileType type)
+ {
+ mediaSource.Container = StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(mediaSource.Container, mediaSource.Path, profile, type);
+ }
+
+ private void SetDeviceSpecificData(
+ BaseItem item,
+ MediaSourceInfo mediaSource,
+ DeviceProfile profile,
+ AuthorizationInfo auth,
+ long? 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)
+ {
+ var streamBuilder = new StreamBuilder(_mediaEncoder, _logger);
+
+ var options = new VideoOptions
+ {
+ MediaSources = new[] { mediaSource },
+ Context = EncodingContext.Streaming,
+ DeviceId = auth.DeviceId,
+ ItemId = item.Id,
+ Profile = profile,
+ MaxAudioChannels = maxAudioChannels
+ };
+
+ if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase))
+ {
+ options.MediaSourceId = mediaSourceId;
+ options.AudioStreamIndex = audioStreamIndex;
+ options.SubtitleStreamIndex = subtitleStreamIndex;
+ }
+
+ var user = _userManager.GetUserById(userId);
+
+ if (!enableDirectPlay)
+ {
+ mediaSource.SupportsDirectPlay = false;
+ }
+
+ if (!enableDirectStream)
+ {
+ 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));
+ }
+
+ // Beginning of Playback Determination: Attempt DirectPlay first
+ if (mediaSource.SupportsDirectPlay)
+ {
+ if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
+ {
+ mediaSource.SupportsDirectPlay = false;
+ }
+ else
+ {
+ var supportsDirectStream = mediaSource.SupportsDirectStream;
+
+ // Dummy this up to fool StreamBuilder
+ mediaSource.SupportsDirectStream = true;
+ options.MaxBitrate = maxBitrate;
+
+ if (item is Audio)
+ {
+ if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
+ {
+ options.ForceDirectPlay = true;
+ }
+ }
+ else if (item is Video)
+ {
+ if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)
+ && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)
+ && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
+ {
+ options.ForceDirectPlay = true;
+ }
+ }
+
+ // The MediaSource supports direct stream, now test to see if the client supports it
+ var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
+ ? streamBuilder.BuildAudioItem(options)
+ : streamBuilder.BuildVideoItem(options);
+
+ if (streamInfo == null || !streamInfo.IsDirectStream)
+ {
+ mediaSource.SupportsDirectPlay = false;
+ }
+
+ // Set this back to what it was
+ mediaSource.SupportsDirectStream = supportsDirectStream;
+
+ if (streamInfo != null)
+ {
+ SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+ }
+ }
+ }
+
+ if (mediaSource.SupportsDirectStream)
+ {
+ if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
+ {
+ mediaSource.SupportsDirectStream = false;
+ }
+ else
+ {
+ options.MaxBitrate = GetMaxBitrate(maxBitrate, user);
+
+ if (item is Audio)
+ {
+ if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
+ {
+ options.ForceDirectStream = true;
+ }
+ }
+ else if (item is Video)
+ {
+ if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)
+ && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)
+ && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
+ {
+ options.ForceDirectStream = true;
+ }
+ }
+
+ // The MediaSource supports direct stream, now test to see if the client supports it
+ var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
+ ? streamBuilder.BuildAudioItem(options)
+ : streamBuilder.BuildVideoItem(options);
+
+ if (streamInfo == null || !streamInfo.IsDirectStream)
+ {
+ mediaSource.SupportsDirectStream = false;
+ }
+
+ if (streamInfo != null)
+ {
+ SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+ }
+ }
+ }
+
+ if (mediaSource.SupportsTranscoding)
+ {
+ options.MaxBitrate = GetMaxBitrate(maxBitrate, user);
+
+ // The MediaSource supports direct stream, now test to see if the client supports it
+ var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
+ ? streamBuilder.BuildAudioItem(options)
+ : streamBuilder.BuildVideoItem(options);
+
+ if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
+ {
+ if (streamInfo != null)
+ {
+ streamInfo.PlaySessionId = playSessionId;
+ streamInfo.StartPositionTicks = startTimeTicks;
+ mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
+ mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
+ mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
+ mediaSource.TranscodingContainer = streamInfo.Container;
+ mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
+
+ // Do this after the above so that StartPositionTicks is set
+ SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+ }
+ }
+ else
+ {
+ if (streamInfo != null)
+ {
+ streamInfo.PlaySessionId = playSessionId;
+
+ if (streamInfo.PlayMethod == PlayMethod.Transcode)
+ {
+ streamInfo.StartPositionTicks = startTimeTicks;
+ mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
+
+ if (!allowVideoStreamCopy)
+ {
+ mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
+ }
+
+ if (!allowAudioStreamCopy)
+ {
+ mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
+ }
+
+ mediaSource.TranscodingContainer = streamInfo.Container;
+ mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
+ }
+
+ if (!allowAudioStreamCopy)
+ {
+ mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
+ }
+
+ mediaSource.TranscodingContainer = streamInfo.Container;
+ mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
+
+ // Do this after the above so that StartPositionTicks is set
+ SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+ }
+ }
+ }
+
+ foreach (var attachment in mediaSource.MediaAttachments)
+ {
+ attachment.DeliveryUrl = string.Format(
+ CultureInfo.InvariantCulture,
+ "/Videos/{0}/{1}/Attachments/{2}",
+ item.Id,
+ mediaSource.Id,
+ attachment.Index);
+ }
+ }
+
+ private async Task<LiveStreamResponse> OpenMediaSource(LiveStreamRequest request)
+ {
+ var authInfo = _authContext.GetAuthorizationInfo(Request);
+
+ var result = await _mediaSourceManager.OpenLiveStream(request, CancellationToken.None).ConfigureAwait(false);
+
+ var profile = request.DeviceProfile;
+ if (profile == null)
+ {
+ var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
+ if (caps != null)
+ {
+ profile = caps.DeviceProfile;
+ }
+ }
+
+ if (profile != null)
+ {
+ var item = _libraryManager.GetItemById(request.ItemId);
+
+ SetDeviceSpecificData(
+ item,
+ result.MediaSource,
+ profile,
+ authInfo,
+ 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);
+ }
+ else
+ {
+ if (!string.IsNullOrWhiteSpace(result.MediaSource.TranscodingUrl))
+ {
+ result.MediaSource.TranscodingUrl += "&LiveStreamId=" + result.MediaSource.LiveStreamId;
+ }
+ }
+
+ // here was a check if (result.MediaSource != null) but Rider said it will never be null
+ NormalizeMediaSourceContainer(result.MediaSource, profile!, DlnaProfileType.Video);
+
+ return result;
+ }
+
+ 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 long? GetMaxBitrate(long? clientMaxBitrate, User user)
+ {
+ var maxBitrate = clientMaxBitrate;
+ var remoteClientMaxBitrate = user?.RemoteClientBitrateLimit ?? 0;
+
+ if (remoteClientMaxBitrate <= 0)
+ {
+ remoteClientMaxBitrate = _serverConfigurationManager.Configuration.RemoteClientBitrateLimit;
+ }
+
+ if (remoteClientMaxBitrate > 0)
+ {
+ var isInLocalNetwork = _networkManager.IsInLocalNetwork(Request.HttpContext.Connection.RemoteIpAddress.ToString());
+
+ _logger.LogInformation("RemoteClientBitrateLimit: {0}, RemoteIp: {1}, IsInLocalNetwork: {2}", remoteClientMaxBitrate, Request.HttpContext.Connection.RemoteIpAddress.ToString(), isInLocalNetwork);
+ if (!isInLocalNetwork)
+ {
+ maxBitrate = Math.Min(maxBitrate ?? remoteClientMaxBitrate, remoteClientMaxBitrate);
+ }
+ }
+
+ return maxBitrate;
+ }
+
+ private 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();
+ }
+ }
+}
diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs
new file mode 100644
index 000000000..a9af1a2c3
--- /dev/null
+++ b/Jellyfin.Api/Controllers/MoviesController.cs
@@ -0,0 +1,329 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using Jellyfin.Api.Constants;
+using Jellyfin.Api.Extensions;
+using Jellyfin.Data.Entities;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+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(Policy = Policies.DefaultAuthorization)]
+ 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] string? parentId,
+ [FromQuery] string? fields,
+ [FromQuery] int categoryLimit = 5,
+ [FromQuery] int itemLimit = 8)
+ {
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request);
+
+ var categories = new List<RecommendationDto>();
+
+ var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? Guid.Empty : new Guid(parentId);
+
+ var query = new InternalItemsQuery(user)
+ {
+ IncludeItemTypes = new[]
+ {
+ nameof(Movie),
+ // typeof(Trailer).Name,
+ // typeof(LiveTvProgram).Name
+ },
+ // IsMovie = true
+ OrderBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.Random }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(),
+ Limit = 7,
+ ParentId = parentIdGuid,
+ Recursive = true,
+ IsPlayed = true,
+ DtoOptions = dtoOptions
+ };
+
+ var recentlyPlayedMovies = _libraryManager.GetItemList(query);
+
+ var itemTypes = new List<string> { nameof(Movie) };
+ if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
+ {
+ itemTypes.Add(nameof(Trailer));
+ itemTypes.Add(nameof(LiveTvProgram));
+ }
+
+ var likedMovies = _libraryManager.GetItemList(new InternalItemsQuery(user)
+ {
+ IncludeItemTypes = itemTypes.ToArray(),
+ IsMovie = true,
+ OrderBy = new[] { ItemSortBy.Random }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(),
+ Limit = 10,
+ IsFavoriteOrLiked = true,
+ ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id).ToArray(),
+ EnableGroupByMetadataKey = true,
+ ParentId = parentIdGuid,
+ Recursive = true,
+ DtoOptions = dtoOptions
+ });
+
+ var mostRecentMovies = recentlyPlayedMovies.Take(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));
+ }
+
+ private IEnumerable<RecommendationDto> GetWithDirector(
+ User? user,
+ IEnumerable<string> names,
+ int itemLimit,
+ DtoOptions dtoOptions,
+ RecommendationType type)
+ {
+ var itemTypes = new List<string> { nameof(MediaBrowser.Controller.Entities.Movies.Movie) };
+ if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
+ {
+ itemTypes.Add(nameof(Trailer));
+ itemTypes.Add(nameof(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
+ }).GroupBy(i => i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture))
+ .Select(x => x.First())
+ .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<string> { nameof(Movie) };
+ if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
+ {
+ itemTypes.Add(nameof(Trailer));
+ itemTypes.Add(nameof(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
+ }).GroupBy(i => i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture))
+ .Select(x => x.First())
+ .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<string> { nameof(Movie) };
+ if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
+ {
+ itemTypes.Add(nameof(Trailer));
+ itemTypes.Add(nameof(LiveTvProgram));
+ }
+
+ foreach (var item in baselineItems)
+ {
+ var similar = _libraryManager.GetItemList(new InternalItemsQuery(user)
+ {
+ Limit = itemLimit,
+ IncludeItemTypes = itemTypes.ToArray(),
+ IsMovie = true,
+ SimilarTo = item,
+ 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
+ {
+ ExcludePersonTypes = 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
+ {
+ PersonTypes = new[] { PersonType.Director }
+ });
+
+ 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 000000000..0d319137a
--- /dev/null
+++ b/Jellyfin.Api/Controllers/MusicGenresController.cs
@@ -0,0 +1,313 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using Jellyfin.Api.Constants;
+using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
+using Jellyfin.Data.Entities;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+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 music genres controller.
+ /// </summary>
+ [Authorize(Policy = Policies.DefaultAuthorization)]
+ 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="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">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="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="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="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 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="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="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]
+ public ActionResult<QueryResult<BaseItemDto>> GetMusicGenres(
+ [FromQuery] double? minCommunityRating,
+ [FromQuery] int? startIndex,
+ [FromQuery] int? limit,
+ [FromQuery] string? searchTerm,
+ [FromQuery] string? parentId,
+ [FromQuery] string? fields,
+ [FromQuery] string? excludeItemTypes,
+ [FromQuery] string? includeItemTypes,
+ [FromQuery] string? filters,
+ [FromQuery] bool? isFavorite,
+ [FromQuery] string? mediaTypes,
+ [FromQuery] string? genres,
+ [FromQuery] string? genreIds,
+ [FromQuery] string? officialRatings,
+ [FromQuery] string? tags,
+ [FromQuery] string? years,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] int? imageTypeLimit,
+ [FromQuery] string? enableImageTypes,
+ [FromQuery] string? person,
+ [FromQuery] string? personIds,
+ [FromQuery] string? personTypes,
+ [FromQuery] string? studios,
+ [FromQuery] string? studioIds,
+ [FromQuery] Guid? userId,
+ [FromQuery] string? nameStartsWithOrGreater,
+ [FromQuery] string? nameStartsWith,
+ [FromQuery] string? nameLessThan,
+ [FromQuery] bool? enableImages = true,
+ [FromQuery] bool enableTotalRecordCount = true)
+ {
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request)
+ .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
+
+ User? user = null;
+ BaseItem parentItem;
+
+ if (userId.HasValue && !userId.Equals(Guid.Empty))
+ {
+ user = _userManager.GetUserById(userId.Value);
+ parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
+ }
+ else
+ {
+ parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId);
+ }
+
+ var query = new InternalItemsQuery(user)
+ {
+ ExcludeItemTypes = RequestHelpers.Split(excludeItemTypes, ',', true),
+ IncludeItemTypes = RequestHelpers.Split(includeItemTypes, ',', true),
+ MediaTypes = RequestHelpers.Split(mediaTypes, ',', true),
+ StartIndex = startIndex,
+ Limit = limit,
+ IsFavorite = isFavorite,
+ NameLessThan = nameLessThan,
+ NameStartsWith = nameStartsWith,
+ NameStartsWithOrGreater = nameStartsWithOrGreater,
+ Tags = RequestHelpers.Split(tags, '|', true),
+ OfficialRatings = RequestHelpers.Split(officialRatings, '|', true),
+ Genres = RequestHelpers.Split(genres, '|', true),
+ GenreIds = RequestHelpers.GetGuids(genreIds),
+ StudioIds = RequestHelpers.GetGuids(studioIds),
+ Person = person,
+ PersonIds = RequestHelpers.GetGuids(personIds),
+ PersonTypes = RequestHelpers.Split(personTypes, ',', true),
+ Years = RequestHelpers.Split(years, ',', true).Select(y => Convert.ToInt32(y, CultureInfo.InvariantCulture)).ToArray(),
+ MinCommunityRating = minCommunityRating,
+ DtoOptions = dtoOptions,
+ SearchTerm = searchTerm,
+ EnableTotalRecordCount = enableTotalRecordCount
+ };
+
+ if (!string.IsNullOrWhiteSpace(parentId))
+ {
+ if (parentItem is Folder)
+ {
+ query.AncestorIds = new[] { new Guid(parentId) };
+ }
+ else
+ {
+ query.ItemIds = new[] { new Guid(parentId) };
+ }
+ }
+
+ // Studios
+ if (!string.IsNullOrEmpty(studios))
+ {
+ query.StudioIds = studios.Split('|')
+ .Select(i =>
+ {
+ try
+ {
+ return _libraryManager.GetStudio(i);
+ }
+ catch
+ {
+ return null;
+ }
+ }).Where(i => i != null)
+ .Select(i => i!.Id)
+ .ToArray();
+ }
+
+ foreach (var filter in RequestHelpers.GetFilters(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.GetMusicGenres(query);
+
+ var dtos = result.Items.Select(i =>
+ {
+ var (baseItem, counts) = i;
+ var dto = _dtoService.GetItemByNameDto(baseItem, dtoOptions, null, user);
+
+ if (!string.IsNullOrWhiteSpace(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>
+ {
+ Items = dtos.ToArray(),
+ TotalRecordCount = result.TotalRecordCount
+ };
+ }
+
+ /// <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] string genreName, [FromQuery] Guid? userId)
+ {
+ var dtoOptions = new DtoOptions().AddClientFields(Request);
+
+ MusicGenre item;
+
+ if (genreName.IndexOf(BaseItem.SlugChar, StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ item = GetItemFromSlugName<MusicGenre>(_libraryManager, genreName, dtoOptions);
+ }
+ else
+ {
+ item = _libraryManager.GetMusicGenre(genreName);
+ }
+
+ if (userId.HasValue && !userId.Equals(Guid.Empty))
+ {
+ 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)
+ where T : BaseItem, new()
+ {
+ var result = libraryManager.GetItemList(new InternalItemsQuery
+ {
+ Name = name.Replace(BaseItem.SlugChar, '&'),
+ IncludeItemTypes = new[] { typeof(T).Name },
+ DtoOptions = dtoOptions
+ }).OfType<T>().FirstOrDefault();
+
+ result ??= libraryManager.GetItemList(new InternalItemsQuery
+ {
+ Name = name.Replace(BaseItem.SlugChar, '/'),
+ IncludeItemTypes = new[] { typeof(T).Name },
+ DtoOptions = dtoOptions
+ }).OfType<T>().FirstOrDefault();
+
+ result ??= libraryManager.GetItemList(new InternalItemsQuery
+ {
+ Name = name.Replace(BaseItem.SlugChar, '?'),
+ IncludeItemTypes = new[] { typeof(T).Name },
+ DtoOptions = dtoOptions
+ }).OfType<T>().FirstOrDefault();
+
+ return result;
+ }
+ }
+}
diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs
index cfa7545c9..1bb39b5f7 100644
--- a/Jellyfin.Api/Controllers/NotificationsController.cs
+++ b/Jellyfin.Api/Controllers/NotificationsController.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Jellyfin.Api.Models.NotificationDtos;
@@ -93,8 +92,8 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Admin")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult CreateAdminNotification(
- [FromQuery] string name,
- [FromQuery] string description,
+ [FromQuery] string? name,
+ [FromQuery] string? description,
[FromQuery] string? url,
[FromQuery] NotificationLevel? level)
{
diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs
index 486575d23..a6c552790 100644
--- a/Jellyfin.Api/Controllers/PackageController.cs
+++ b/Jellyfin.Api/Controllers/PackageController.cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using MediaBrowser.Common.Updates;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Updates;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
@@ -20,14 +21,17 @@ namespace Jellyfin.Api.Controllers
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 <see cref="IInstallationManager"/>Installation Manager.</param>
- public PackageController(IInstallationManager installationManager)
+ /// <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>
@@ -40,7 +44,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("/{name}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<PackageInfo>> GetPackageInfo(
- [FromRoute] [Required] string name,
+ [FromRoute] [Required] string? name,
[FromQuery] string? assemblyGuid)
{
var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
@@ -80,9 +84,9 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize(Policy = Policies.RequiresElevation)]
public async Task<ActionResult> InstallPackage(
- [FromRoute] [Required] string name,
- [FromQuery] string assemblyGuid,
- [FromQuery] string version)
+ [FromRoute] [Required] string? name,
+ [FromQuery] string? assemblyGuid,
+ [FromQuery] string? version)
{
var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
var package = _installationManager.GetCompatibleVersions(
@@ -110,11 +114,39 @@ namespace Jellyfin.Api.Controllers
[HttpDelete("/Installing/{packageId}")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public IActionResult CancelPackageInstallation(
+ 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")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult<IEnumerable<RepositoryInfo>> GetRepositories()
+ {
+ return _serverConfigurationManager.Configuration.PluginRepositories;
+ }
+
+ /// <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>
+ [HttpOptions("/Repositories")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ public ActionResult SetRepositories([FromBody] List<RepositoryInfo> repositoryInfos)
+ {
+ _serverConfigurationManager.Configuration.PluginRepositories = repositoryInfos;
+ return NoContent();
+ }
}
}
diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs
new file mode 100644
index 000000000..23cc23ce7
--- /dev/null
+++ b/Jellyfin.Api/Controllers/PersonsController.cs
@@ -0,0 +1,282 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
+using Jellyfin.Data.Entities;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Querying;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Jellyfin.Api.Controllers
+{
+ /// <summary>
+ /// Persons controller.
+ /// </summary>
+ 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 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">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="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="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="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 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="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="enableImages">Optional, include image information in output.</param>
+ /// <param name="enableTotalRecordCount">Optional. Include total record count.</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] double? minCommunityRating,
+ [FromQuery] int? startIndex,
+ [FromQuery] int? limit,
+ [FromQuery] string? searchTerm,
+ [FromQuery] string? parentId,
+ [FromQuery] string? fields,
+ [FromQuery] string? excludeItemTypes,
+ [FromQuery] string? includeItemTypes,
+ [FromQuery] string? filters,
+ [FromQuery] bool? isFavorite,
+ [FromQuery] string? mediaTypes,
+ [FromQuery] string? genres,
+ [FromQuery] string? genreIds,
+ [FromQuery] string? officialRatings,
+ [FromQuery] string? tags,
+ [FromQuery] string? years,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] int? imageTypeLimit,
+ [FromQuery] string? enableImageTypes,
+ [FromQuery] string? person,
+ [FromQuery] string? personIds,
+ [FromQuery] string? personTypes,
+ [FromQuery] string? studios,
+ [FromQuery] string? studioIds,
+ [FromQuery] Guid? userId,
+ [FromQuery] string? nameStartsWithOrGreater,
+ [FromQuery] string? nameStartsWith,
+ [FromQuery] string? nameLessThan,
+ [FromQuery] bool? enableImages = true,
+ [FromQuery] bool enableTotalRecordCount = true)
+ {
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request)
+ .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
+
+ User? user = null;
+ BaseItem parentItem;
+
+ if (userId.HasValue && !userId.Equals(Guid.Empty))
+ {
+ user = _userManager.GetUserById(userId.Value);
+ parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
+ }
+ else
+ {
+ parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId);
+ }
+
+ var query = new InternalItemsQuery(user)
+ {
+ ExcludeItemTypes = RequestHelpers.Split(excludeItemTypes, ',', true),
+ IncludeItemTypes = RequestHelpers.Split(includeItemTypes, ',', true),
+ MediaTypes = RequestHelpers.Split(mediaTypes, ',', true),
+ StartIndex = startIndex,
+ Limit = limit,
+ IsFavorite = isFavorite,
+ NameLessThan = nameLessThan,
+ NameStartsWith = nameStartsWith,
+ NameStartsWithOrGreater = nameStartsWithOrGreater,
+ Tags = RequestHelpers.Split(tags, '|', true),
+ OfficialRatings = RequestHelpers.Split(officialRatings, '|', true),
+ Genres = RequestHelpers.Split(genres, '|', true),
+ GenreIds = RequestHelpers.GetGuids(genreIds),
+ StudioIds = RequestHelpers.GetGuids(studioIds),
+ Person = person,
+ PersonIds = RequestHelpers.GetGuids(personIds),
+ PersonTypes = RequestHelpers.Split(personTypes, ',', true),
+ Years = RequestHelpers.Split(years, ',', true).Select(y => Convert.ToInt32(y, CultureInfo.InvariantCulture)).ToArray(),
+ MinCommunityRating = minCommunityRating,
+ DtoOptions = dtoOptions,
+ SearchTerm = searchTerm,
+ EnableTotalRecordCount = enableTotalRecordCount
+ };
+
+ if (!string.IsNullOrWhiteSpace(parentId))
+ {
+ if (parentItem is Folder)
+ {
+ query.AncestorIds = new[] { new Guid(parentId) };
+ }
+ else
+ {
+ query.ItemIds = new[] { new Guid(parentId) };
+ }
+ }
+
+ // Studios
+ if (!string.IsNullOrEmpty(studios))
+ {
+ query.StudioIds = studios.Split('|')
+ .Select(i =>
+ {
+ try
+ {
+ return _libraryManager.GetStudio(i);
+ }
+ catch
+ {
+ return null;
+ }
+ }).Where(i => i != null)
+ .Select(i => i!.Id)
+ .ToArray();
+ }
+
+ foreach (var filter in RequestHelpers.GetFilters(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 = new QueryResult<(BaseItem, ItemCounts)>();
+
+ var dtos = result.Items.Select(i =>
+ {
+ var (baseItem, counts) = i;
+ var dto = _dtoService.GetItemByNameDto(baseItem, dtoOptions, null, user);
+
+ if (!string.IsNullOrWhiteSpace(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>
+ {
+ Items = dtos.ToArray(),
+ TotalRecordCount = result.TotalRecordCount
+ };
+ }
+
+ /// <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] string name, [FromQuery] Guid? userId)
+ {
+ var dtoOptions = new DtoOptions()
+ .AddClientFields(Request);
+
+ var item = _libraryManager.GetPerson(name);
+ if (item == null)
+ {
+ return NotFound();
+ }
+
+ if (userId.HasValue && !userId.Equals(Guid.Empty))
+ {
+ 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
index 2dc0d2dc7..cf4660494 100644
--- a/Jellyfin.Api/Controllers/PlaylistsController.cs
+++ b/Jellyfin.Api/Controllers/PlaylistsController.cs
@@ -84,11 +84,11 @@ namespace Jellyfin.Api.Controllers
[HttpPost("{playlistId}/Items")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult AddToPlaylist(
- [FromRoute] string playlistId,
- [FromQuery] string ids,
- [FromQuery] Guid userId)
+ [FromRoute] string? playlistId,
+ [FromQuery] string? ids,
+ [FromQuery] Guid? userId)
{
- _playlistManager.AddToPlaylist(playlistId, RequestHelpers.GetGuids(ids), userId);
+ _playlistManager.AddToPlaylist(playlistId, RequestHelpers.GetGuids(ids), userId ?? Guid.Empty);
return NoContent();
}
@@ -103,8 +103,8 @@ namespace Jellyfin.Api.Controllers
[HttpPost("{playlistId}/Items/{itemId}/Move/{newIndex}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult MoveItem(
- [FromRoute] string playlistId,
- [FromRoute] string itemId,
+ [FromRoute] string? playlistId,
+ [FromRoute] string? itemId,
[FromRoute] int newIndex)
{
_playlistManager.MoveItem(playlistId, itemId, newIndex);
@@ -120,7 +120,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="NoContentResult"/> on success.</returns>
[HttpDelete("{playlistId}/Items")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult RemoveFromPlaylist([FromRoute] string playlistId, [FromQuery] string entryIds)
+ public ActionResult RemoveFromPlaylist([FromRoute] string? playlistId, [FromQuery] string? entryIds)
{
_playlistManager.RemoveFromPlaylist(playlistId, RequestHelpers.Split(entryIds, ',', true));
return NoContent();
@@ -147,11 +147,11 @@ namespace Jellyfin.Api.Controllers
[FromRoute] Guid userId,
[FromRoute] int? startIndex,
[FromRoute] int? limit,
- [FromRoute] string fields,
+ [FromRoute] string? fields,
[FromRoute] bool? enableImages,
[FromRoute] bool? enableUserData,
[FromRoute] int? imageTypeLimit,
- [FromRoute] string enableImageTypes)
+ [FromRoute] string? enableImageTypes)
{
var playlist = (Playlist)_libraryManager.GetItemById(playlistId);
if (playlist == null)
diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs
new file mode 100644
index 000000000..9fcf04199
--- /dev/null
+++ b/Jellyfin.Api/Controllers/PlaystateController.cs
@@ -0,0 +1,372 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading.Tasks;
+using Jellyfin.Api.Constants;
+using Jellyfin.Api.Helpers;
+using Jellyfin.Data.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.IO;
+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>
+ [Authorize(Policy = Policies.DefaultAuthorization)]
+ public class PlaystateController : BaseJellyfinApiController
+ {
+ private readonly IUserManager _userManager;
+ private readonly IUserDataManager _userDataRepository;
+ private readonly ILibraryManager _libraryManager;
+ private readonly ISessionManager _sessionManager;
+ private readonly IAuthorizationContext _authContext;
+ private readonly ILogger<PlaystateController> _logger;
+ private readonly TranscodingJobHelper _transcodingJobHelper;
+
+ /// <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="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
+ /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
+ /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
+ /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+ public PlaystateController(
+ IUserManager userManager,
+ IUserDataManager userDataRepository,
+ ILibraryManager libraryManager,
+ ISessionManager sessionManager,
+ IAuthorizationContext authContext,
+ ILoggerFactory loggerFactory,
+ IMediaSourceManager mediaSourceManager,
+ IFileSystem fileSystem)
+ {
+ _userManager = userManager;
+ _userDataRepository = userDataRepository;
+ _libraryManager = libraryManager;
+ _sessionManager = sessionManager;
+ _authContext = authContext;
+ _logger = loggerFactory.CreateLogger<PlaystateController>();
+
+ _transcodingJobHelper = new TranscodingJobHelper(
+ loggerFactory.CreateLogger<TranscodingJobHelper>(),
+ mediaSourceManager,
+ fileSystem);
+ }
+
+ /// <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>
+ /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
+ [HttpPost("/Users/{userId}/PlayedItems/{itemId}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult<UserItemDataDto> MarkPlayedItem(
+ [FromRoute] Guid userId,
+ [FromRoute] Guid itemId,
+ [FromQuery] DateTime? datePlayed)
+ {
+ var user = _userManager.GetUserById(userId);
+ var session = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
+ var dto = UpdatePlayedStatus(user, itemId, true, datePlayed);
+ foreach (var additionalUserInfo in session.AdditionalUsers)
+ {
+ var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId);
+ UpdatePlayedStatus(additionalUser, itemId, true, datePlayed);
+ }
+
+ 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>
+ /// <returns>A <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
+ [HttpDelete("/Users/{userId}/PlayedItem/{itemId}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult<UserItemDataDto> MarkUnplayedItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
+ {
+ var user = _userManager.GetUserById(userId);
+ var session = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
+ var dto = UpdatePlayedStatus(user, itemId, false, null);
+ foreach (var additionalUserInfo in session.AdditionalUsers)
+ {
+ var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId);
+ UpdatePlayedStatus(additionalUser, itemId, false, null);
+ }
+
+ return dto;
+ }
+
+ /// <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 = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
+ 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 = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
+ 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] string playSessionId)
+ {
+ _transcodingJobHelper.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 _transcodingJobHelper.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false);
+ }
+
+ playbackStopInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
+ await _sessionManager.OnPlaybackStopped(playbackStopInfo).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)]
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
+ public async Task<ActionResult> OnPlaybackStart(
+ [FromRoute] Guid userId,
+ [FromRoute] 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,
+ PlaySessionId = playSessionId,
+ LiveStreamId = liveStreamId
+ };
+
+ playbackStartInfo.PlayMethod = ValidatePlayMethod(playbackStartInfo.PlayMethod, playbackStartInfo.PlaySessionId);
+ playbackStartInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
+ await _sessionManager.OnPlaybackStart(playbackStartInfo).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)]
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
+ public async Task<ActionResult> OnPlaybackProgress(
+ [FromRoute] Guid userId,
+ [FromRoute] 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,
+ PlaySessionId = playSessionId,
+ LiveStreamId = liveStreamId,
+ RepeatMode = repeatMode
+ };
+
+ playbackProgressInfo.PlayMethod = ValidatePlayMethod(playbackProgressInfo.PlayMethod, playbackProgressInfo.PlaySessionId);
+ playbackProgressInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
+ await _sessionManager.OnPlaybackProgress(playbackProgressInfo).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)]
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
+ public async Task<ActionResult> OnPlaybackStopped(
+ [FromRoute] Guid userId,
+ [FromRoute] 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 _transcodingJobHelper.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false);
+ }
+
+ playbackStopInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
+ await _sessionManager.OnPlaybackStopped(playbackStopInfo).ConfigureAwait(false);
+ return NoContent();
+ }
+
+ /// <summary>
+ /// Updates the played status.
+ /// </summary>
+ /// <param name="user">The user.</param>
+ /// <param name="itemId">The item id.</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, Guid itemId, bool wasPlayed, DateTime? datePlayed)
+ {
+ var item = _libraryManager.GetItemById(itemId);
+
+ 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 : _transcodingJobHelper.GetTranscodingJob(playSessionId);
+ if (job == null)
+ {
+ return PlayMethod.DirectPlay;
+ }
+ }
+
+ return method;
+ }
+ }
+}
diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs
index fd48983ea..48bb867ac 100644
--- a/Jellyfin.Api/Controllers/PluginsController.cs
+++ b/Jellyfin.Api/Controllers/PluginsController.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
@@ -166,7 +165,7 @@ namespace Jellyfin.Api.Controllers
[Obsolete("This endpoint should not be used.")]
[HttpPost("RegistrationRecords/{name}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<MBRegistrationRecord> GetRegistrationStatus([FromRoute] string name)
+ public ActionResult<MBRegistrationRecord> GetRegistrationStatus([FromRoute] string? name)
{
return new MBRegistrationRecord
{
@@ -188,7 +187,7 @@ namespace Jellyfin.Api.Controllers
[Obsolete("Paid plugins are not supported")]
[HttpGet("/Registrations/{name}")]
[ProducesResponseType(StatusCodes.Status501NotImplemented)]
- public ActionResult GetRegistration([FromRoute] string name)
+ public ActionResult GetRegistration([FromRoute] string? name)
{
// TODO Once we have proper apps and plugins and decide to break compatibility with paid plugins,
// delete all these registration endpoints. They are only kept for compatibility.
diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs
index a0d14be7a..1b26163cf 100644
--- a/Jellyfin.Api/Controllers/RemoteImageController.cs
+++ b/Jellyfin.Api/Controllers/RemoteImageController.cs
@@ -74,7 +74,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] string providerName,
- [FromQuery] bool includeAllLanguages)
+ [FromQuery] bool includeAllLanguages = false)
{
var item = _libraryManager.GetItemById(itemId);
if (item == null)
@@ -208,7 +208,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> DownloadRemoteImage(
[FromRoute] Guid itemId,
[FromQuery, BindRequired] ImageType type,
- [FromQuery] string imageUrl)
+ [FromQuery] string? imageUrl)
{
var item = _libraryManager.GetItemById(itemId);
if (item == null)
diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs
new file mode 100644
index 000000000..3df325e3b
--- /dev/null
+++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs
@@ -0,0 +1,161 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Jellyfin.Api.Constants;
+using MediaBrowser.Model.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+
+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<IScheduledTaskWorker> 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 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] string? taskId)
+ {
+ var task = _taskManager.ScheduledTasks.FirstOrDefault(i =>
+ string.Equals(i.Id, taskId, StringComparison.OrdinalIgnoreCase));
+
+ if (task == 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] string? taskId)
+ {
+ var task = _taskManager.ScheduledTasks.FirstOrDefault(o =>
+ o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase));
+
+ if (task == 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] string? taskId)
+ {
+ var task = _taskManager.ScheduledTasks.FirstOrDefault(o =>
+ o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase));
+
+ if (task == 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] string? taskId,
+ [FromBody, BindRequired] TaskTriggerInfo[] triggerInfos)
+ {
+ var task = _taskManager.ScheduledTasks.FirstOrDefault(o =>
+ o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase));
+ if (task == null)
+ {
+ return NotFound();
+ }
+
+ task.Triggers = triggerInfos;
+ return NoContent();
+ }
+ }
+}
diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs
index d971889db..2cbd32d2f 100644
--- a/Jellyfin.Api/Controllers/SearchController.cs
+++ b/Jellyfin.Api/Controllers/SearchController.cs
@@ -80,12 +80,12 @@ namespace Jellyfin.Api.Controllers
public ActionResult<SearchHintResult> Get(
[FromQuery] int? startIndex,
[FromQuery] int? limit,
- [FromQuery] Guid userId,
- [FromQuery, Required] string searchTerm,
- [FromQuery] string includeItemTypes,
- [FromQuery] string excludeItemTypes,
- [FromQuery] string mediaTypes,
- [FromQuery] string parentId,
+ [FromQuery] Guid? userId,
+ [FromQuery, Required] string? searchTerm,
+ [FromQuery] string? includeItemTypes,
+ [FromQuery] string? excludeItemTypes,
+ [FromQuery] string? mediaTypes,
+ [FromQuery] string? parentId,
[FromQuery] bool? isMovie,
[FromQuery] bool? isSeries,
[FromQuery] bool? isNews,
@@ -107,7 +107,7 @@ namespace Jellyfin.Api.Controllers
IncludePeople = includePeople,
IncludeStudios = includeStudios,
StartIndex = startIndex,
- UserId = userId,
+ UserId = userId ?? Guid.Empty,
IncludeItemTypes = RequestHelpers.Split(includeItemTypes, ',', true),
ExcludeItemTypes = RequestHelpers.Split(excludeItemTypes, ',', true),
MediaTypes = RequestHelpers.Split(mediaTypes, ',', true),
diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs
index 39da4178d..0c98a8e71 100644
--- a/Jellyfin.Api/Controllers/SessionController.cs
+++ b/Jellyfin.Api/Controllers/SessionController.cs
@@ -61,8 +61,8 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<SessionInfo>> GetSessions(
- [FromQuery] Guid controllableByUserId,
- [FromQuery] string deviceId,
+ [FromQuery] Guid? controllableByUserId,
+ [FromQuery] string? deviceId,
[FromQuery] int? activeWithinSeconds)
{
var result = _sessionManager.Sessions;
@@ -72,15 +72,15 @@ namespace Jellyfin.Api.Controllers
result = result.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase));
}
- if (!controllableByUserId.Equals(Guid.Empty))
+ if (controllableByUserId.HasValue && !controllableByUserId.Equals(Guid.Empty))
{
result = result.Where(i => i.SupportsRemoteControl);
- var user = _userManager.GetUserById(controllableByUserId);
+ var user = _userManager.GetUserById(controllableByUserId.Value);
if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers))
{
- result = result.Where(i => i.UserId.Equals(Guid.Empty) || i.ContainsUser(controllableByUserId));
+ result = result.Where(i => i.UserId.Equals(Guid.Empty) || i.ContainsUser(controllableByUserId.Value));
}
if (!user.HasPermission(PermissionKind.EnableSharedDeviceControl))
@@ -123,10 +123,10 @@ namespace Jellyfin.Api.Controllers
[HttpPost("/Sessions/{sessionId}/Viewing")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult DisplayContent(
- [FromRoute] string sessionId,
- [FromQuery] string itemType,
- [FromQuery] string itemId,
- [FromQuery] string itemName)
+ [FromRoute] string? sessionId,
+ [FromQuery] string? itemType,
+ [FromQuery] string? itemId,
+ [FromQuery] string? itemName)
{
var command = new BrowseRequest
{
@@ -157,7 +157,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("/Sessions/{sessionId}/Playing")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult Play(
- [FromRoute] string sessionId,
+ [FromRoute] string? sessionId,
[FromQuery] Guid[] itemIds,
[FromQuery] long? startPositionTicks,
[FromQuery] PlayCommand playCommand,
@@ -191,7 +191,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("/Sessions/{sessionId}/Playing/{command}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendPlaystateCommand(
- [FromRoute] string sessionId,
+ [FromRoute] string? sessionId,
[FromBody] PlaystateRequest playstateRequest)
{
_sessionManager.SendPlaystateCommand(
@@ -213,8 +213,8 @@ namespace Jellyfin.Api.Controllers
[HttpPost("/Sessions/{sessionId}/System/{command}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendSystemCommand(
- [FromRoute] string sessionId,
- [FromRoute] string command)
+ [FromRoute] string? sessionId,
+ [FromRoute] string? command)
{
var name = command;
if (Enum.TryParse(name, true, out GeneralCommandType commandType))
@@ -244,8 +244,8 @@ namespace Jellyfin.Api.Controllers
[HttpPost("/Sessions/{sessionId}/Command/{Command}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendGeneralCommand(
- [FromRoute] string sessionId,
- [FromRoute] string command)
+ [FromRoute] string? sessionId,
+ [FromRoute] string? command)
{
var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
@@ -270,7 +270,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("/Sessions/{sessionId}/Command")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendFullGeneralCommand(
- [FromRoute] string sessionId,
+ [FromRoute] string? sessionId,
[FromBody, Required] GeneralCommand command)
{
var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
@@ -303,9 +303,9 @@ namespace Jellyfin.Api.Controllers
[HttpPost("/Sessions/{sessionId}/Message")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendMessageCommand(
- [FromRoute] string sessionId,
- [FromQuery] string text,
- [FromQuery] string header,
+ [FromRoute] string? sessionId,
+ [FromQuery] string? text,
+ [FromQuery] string? header,
[FromQuery] long? timeoutMs)
{
var command = new MessageCommand
@@ -330,7 +330,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("/Sessions/{sessionId}/User/{userId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult AddUserToSession(
- [FromRoute] string sessionId,
+ [FromRoute] string? sessionId,
[FromRoute] Guid userId)
{
_sessionManager.AddAdditionalUser(sessionId, userId);
@@ -347,7 +347,7 @@ namespace Jellyfin.Api.Controllers
[HttpDelete("/Sessions/{sessionId}/User/{userId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult RemoveUserFromSession(
- [FromRoute] string sessionId,
+ [FromRoute] string? sessionId,
[FromRoute] Guid userId)
{
_sessionManager.RemoveAdditionalUser(sessionId, userId);
@@ -368,11 +368,11 @@ namespace Jellyfin.Api.Controllers
[HttpPost("/Sessions/Capabilities")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PostCapabilities(
- [FromQuery] string id,
- [FromQuery] string playableMediaTypes,
- [FromQuery] string supportedCommands,
- [FromQuery] bool supportsMediaControl,
- [FromQuery] bool supportsSync,
+ [FromQuery] string? id,
+ [FromQuery] string? playableMediaTypes,
+ [FromQuery] string? supportedCommands,
+ [FromQuery] bool supportsMediaControl = false,
+ [FromQuery] bool supportsSync = false,
[FromQuery] bool supportsPersistentIdentifier = true)
{
if (string.IsNullOrWhiteSpace(id))
@@ -401,7 +401,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("/Sessions/Capabilities/Full")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PostFullCapabilities(
- [FromQuery] string id,
+ [FromQuery] string? id,
[FromBody, Required] ClientCapabilities capabilities)
{
if (string.IsNullOrWhiteSpace(id))
@@ -424,8 +424,8 @@ namespace Jellyfin.Api.Controllers
[HttpPost("/Sessions/Viewing")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult ReportViewing(
- [FromQuery] string sessionId,
- [FromQuery] string itemId)
+ [FromQuery] string? sessionId,
+ [FromQuery] string? itemId)
{
string session = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs
index d96b0f993..252b5cdd1 100644
--- a/Jellyfin.Api/Controllers/StartupController.cs
+++ b/Jellyfin.Api/Controllers/StartupController.cs
@@ -40,7 +40,6 @@ namespace Jellyfin.Api.Controllers
public ActionResult CompleteWizard()
{
_config.Configuration.IsStartupWizardCompleted = true;
- _config.SetOptimalValues();
_config.SaveConfiguration();
return NoContent();
}
@@ -75,9 +74,9 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Configuration")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult UpdateInitialConfiguration(
- [FromForm] string uiCulture,
- [FromForm] string metadataCountryCode,
- [FromForm] string preferredMetadataLanguage)
+ [FromForm] string? uiCulture,
+ [FromForm] string? metadataCountryCode,
+ [FromForm] string? preferredMetadataLanguage)
{
_config.Configuration.UICulture = uiCulture;
_config.Configuration.MetadataCountryCode = metadataCountryCode;
diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs
new file mode 100644
index 000000000..6f2787d93
--- /dev/null
+++ b/Jellyfin.Api/Controllers/StudiosController.cs
@@ -0,0 +1,277 @@
+using System;
+using System.Linq;
+using Jellyfin.Api.Constants;
+using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
+using Jellyfin.Data.Entities;
+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>
+ /// Studios controller.
+ /// </summary>
+ [Authorize(Policy = Policies.DefaultAuthorization)]
+ 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="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. 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 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. 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="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="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] double? minCommunityRating,
+ [FromQuery] int? startIndex,
+ [FromQuery] int? limit,
+ [FromQuery] string? searchTerm,
+ [FromQuery] string? parentId,
+ [FromQuery] string? fields,
+ [FromQuery] string? excludeItemTypes,
+ [FromQuery] string? includeItemTypes,
+ [FromQuery] string? filters,
+ [FromQuery] bool? isFavorite,
+ [FromQuery] string? mediaTypes,
+ [FromQuery] string? genres,
+ [FromQuery] string? genreIds,
+ [FromQuery] string? officialRatings,
+ [FromQuery] string? tags,
+ [FromQuery] string? years,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] int? imageTypeLimit,
+ [FromQuery] string? enableImageTypes,
+ [FromQuery] string? person,
+ [FromQuery] string? personIds,
+ [FromQuery] string? personTypes,
+ [FromQuery] string? studios,
+ [FromQuery] string? studioIds,
+ [FromQuery] Guid? userId,
+ [FromQuery] string? nameStartsWithOrGreater,
+ [FromQuery] string? nameStartsWith,
+ [FromQuery] string? nameLessThan,
+ [FromQuery] bool? enableImages = true,
+ [FromQuery] bool enableTotalRecordCount = true)
+ {
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request)
+ .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
+
+ User? user = null;
+ BaseItem parentItem;
+
+ if (userId.HasValue && !userId.Equals(Guid.Empty))
+ {
+ user = _userManager.GetUserById(userId.Value);
+ parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
+ }
+ else
+ {
+ parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId);
+ }
+
+ var excludeItemTypesArr = RequestHelpers.Split(excludeItemTypes, ',', true);
+ var includeItemTypesArr = RequestHelpers.Split(includeItemTypes, ',', true);
+ var mediaTypesArr = RequestHelpers.Split(mediaTypes, ',', true);
+
+ var query = new InternalItemsQuery(user)
+ {
+ ExcludeItemTypes = excludeItemTypesArr,
+ IncludeItemTypes = includeItemTypesArr,
+ MediaTypes = mediaTypesArr,
+ StartIndex = startIndex,
+ Limit = limit,
+ IsFavorite = isFavorite,
+ NameLessThan = nameLessThan,
+ NameStartsWith = nameStartsWith,
+ NameStartsWithOrGreater = nameStartsWithOrGreater,
+ Tags = RequestHelpers.Split(tags, ',', true),
+ OfficialRatings = RequestHelpers.Split(officialRatings, ',', true),
+ Genres = RequestHelpers.Split(genres, ',', true),
+ GenreIds = RequestHelpers.GetGuids(genreIds),
+ StudioIds = RequestHelpers.GetGuids(studioIds),
+ Person = person,
+ PersonIds = RequestHelpers.GetGuids(personIds),
+ PersonTypes = RequestHelpers.Split(personTypes, ',', true),
+ Years = RequestHelpers.Split(years, ',', true).Select(int.Parse).ToArray(),
+ MinCommunityRating = minCommunityRating,
+ DtoOptions = dtoOptions,
+ SearchTerm = searchTerm,
+ EnableTotalRecordCount = enableTotalRecordCount
+ };
+
+ if (!string.IsNullOrWhiteSpace(parentId))
+ {
+ if (parentItem is Folder)
+ {
+ query.AncestorIds = new[] { new Guid(parentId) };
+ }
+ else
+ {
+ query.ItemIds = new[] { new Guid(parentId) };
+ }
+ }
+
+ // Studios
+ if (!string.IsNullOrEmpty(studios))
+ {
+ query.StudioIds = studios.Split('|').Select(i =>
+ {
+ try
+ {
+ return _libraryManager.GetStudio(i);
+ }
+ catch
+ {
+ return null;
+ }
+ }).Where(i => i != null).Select(i => i!.Id)
+ .ToArray();
+ }
+
+ foreach (var filter in RequestHelpers.GetFilters(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 = new QueryResult<(BaseItem, ItemCounts)>();
+ var dtos = result.Items.Select(i =>
+ {
+ var (baseItem, itemCounts) = i;
+ var dto = _dtoService.GetItemByNameDto(baseItem, dtoOptions, null, user);
+
+ if (!string.IsNullOrWhiteSpace(includeItemTypes))
+ {
+ 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>
+ {
+ Items = dtos.ToArray(),
+ TotalRecordCount = result.TotalRecordCount
+ };
+ }
+
+ /// <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] string name, [FromQuery] Guid? userId)
+ {
+ var dtoOptions = new DtoOptions().AddClientFields(Request);
+
+ var item = _libraryManager.GetStudio(name);
+ if (userId.HasValue && !userId.Equals(Guid.Empty))
+ {
+ 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
index 95cc39524..1c38b8de5 100644
--- a/Jellyfin.Api/Controllers/SubtitleController.cs
+++ b/Jellyfin.Api/Controllers/SubtitleController.cs
@@ -112,7 +112,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<RemoteSubtitleInfo>>> SearchRemoteSubtitles(
[FromRoute] Guid itemId,
- [FromRoute] string language,
+ [FromRoute] string? language,
[FromQuery] bool? isPerfectMatch)
{
var video = (Video)_libraryManager.GetItemById(itemId);
@@ -132,7 +132,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> DownloadRemoteSubtitles(
[FromRoute] Guid itemId,
- [FromRoute] string subtitleId)
+ [FromRoute] string? subtitleId)
{
var video = (Video)_libraryManager.GetItemById(itemId);
@@ -161,7 +161,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[Produces(MediaTypeNames.Application.Octet)]
- public async Task<ActionResult> GetRemoteSubtitles([FromRoute] string id)
+ public async Task<ActionResult> GetRemoteSubtitles([FromRoute] string? id)
{
var result = await _subtitleManager.GetRemoteSubtitles(id, CancellationToken.None).ConfigureAwait(false);
@@ -186,12 +186,12 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetSubtitle(
[FromRoute, Required] Guid itemId,
- [FromRoute, Required] string mediaSourceId,
+ [FromRoute, Required] string? mediaSourceId,
[FromRoute, Required] int index,
- [FromRoute, Required] string format,
+ [FromRoute, Required] string? format,
[FromQuery] long? endPositionTicks,
- [FromQuery] bool copyTimestamps,
- [FromQuery] bool addVttTimeMap,
+ [FromQuery] bool copyTimestamps = false,
+ [FromQuery] bool addVttTimeMap = false,
[FromRoute] long startPositionTicks = 0)
{
if (string.Equals(format, "js", StringComparison.OrdinalIgnoreCase))
@@ -254,7 +254,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> GetSubtitlePlaylist(
[FromRoute] Guid itemId,
[FromRoute] int index,
- [FromRoute] string mediaSourceId,
+ [FromRoute] string? mediaSourceId,
[FromQuery, Required] int segmentLength)
{
var item = (Video)_libraryManager.GetItemById(itemId);
@@ -324,7 +324,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="Task{Stream}"/> with the new subtitle file.</returns>
private Task<Stream> EncodeSubtitles(
Guid id,
- string mediaSourceId,
+ string? mediaSourceId,
int index,
string format,
long startPositionTicks,
diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs
index e1a99a138..bf3c1e2b1 100644
--- a/Jellyfin.Api/Controllers/SuggestionsController.cs
+++ b/Jellyfin.Api/Controllers/SuggestionsController.cs
@@ -44,9 +44,9 @@ namespace Jellyfin.Api.Controllers
/// <param name="userId">The user id.</param>
/// <param name="mediaType">The media types.</param>
/// <param name="type">The type.</param>
- /// <param name="enableTotalRecordCount">Whether to enable the total record count.</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")]
@@ -55,9 +55,9 @@ namespace Jellyfin.Api.Controllers
[FromRoute] Guid userId,
[FromQuery] string? mediaType,
[FromQuery] string? type,
- [FromQuery] bool enableTotalRecordCount,
[FromQuery] int? startIndex,
- [FromQuery] int? limit)
+ [FromQuery] int? limit,
+ [FromQuery] bool enableTotalRecordCount = false)
{
var user = !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId) : null;
diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs
index e33821b24..bc606f7aa 100644
--- a/Jellyfin.Api/Controllers/SystemController.cs
+++ b/Jellyfin.Api/Controllers/SystemController.cs
@@ -193,7 +193,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Logs/Log")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult GetLogFile([FromQuery, Required] string name)
+ public ActionResult GetLogFile([FromQuery, Required] string? name)
{
var file = _fileSystem.GetFiles(_appPaths.LogDirectoryPath)
.First(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase));
diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs
new file mode 100644
index 000000000..645495551
--- /dev/null
+++ b/Jellyfin.Api/Controllers/TrailersController.cs
@@ -0,0 +1,307 @@
+using System;
+using Jellyfin.Api.Constants;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Dto;
+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 trailers controller.
+ /// </summary>
+ [Authorize(Policy = Policies.DefaultAuthorization)]
+ public class TrailersController : BaseJellyfinApiController
+ {
+ private readonly IUserManager _userManager;
+ private readonly ILibraryManager _libraryManager;
+ private readonly ILogger<ItemsController> _logger;
+ private readonly IDtoService _dtoService;
+ private readonly ILocalizationManager _localizationManager;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TrailersController"/> class.
+ /// </summary>
+ /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> 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="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
+ /// <param name="localizationManager">Instance of the <see cref="ILocalizationManager"/> interface.</param>
+ public TrailersController(
+ ILoggerFactory loggerFactory,
+ IUserManager userManager,
+ ILibraryManager libraryManager,
+ IDtoService dtoService,
+ ILocalizationManager localizationManager)
+ {
+ _userManager = userManager;
+ _libraryManager = libraryManager;
+ _dtoService = dtoService;
+ _localizationManager = localizationManager;
+ _logger = loggerFactory.CreateLogger<ItemsController>();
+ }
+
+ /// <summary>
+ /// Finds movies and trailers similar to a given trailer.
+ /// </summary>
+ /// <param name="userId">The user id.</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 delimeted.</param>
+ /// <param name="excludeLocationTypes">Optional. If specified, results will be filtered based on the LocationType. This allows multiple, comma delimeted.</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="excludeItemIds">Optional. If specified, results will be filtered by exxcluding item ids. This allows multiple, comma delimeted.</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 delimeted. 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 delimeted.</param>
+ /// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimeted. 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 delimeted. 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 delimeted.</param>
+ /// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimeted.</param>
+ /// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimeted.</param>
+ /// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimeted.</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 delimeted.</param>
+ /// <param name="artists">Optional. If specified, results will be filtered based on artists. This allows multiple, pipe delimeted.</param>
+ /// <param name="excludeArtistIds">Optional. If specified, results will be filtered based on artist id. This allows multiple, pipe delimeted.</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 delimeted.</param>
+ /// <param name="albumIds">Optional. If specified, results will be filtered based on album id. This allows multiple, pipe delimeted.</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 delimeted.</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 delimeted.</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 delimeted.</param>
+ /// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimeted.</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("/Trailers")]
+ [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] string? adjacentTo,
+ [FromQuery] int? parentIndexNumber,
+ [FromQuery] bool? hasParentalRating,
+ [FromQuery] bool? isHd,
+ [FromQuery] bool? is4K,
+ [FromQuery] string? locationTypes,
+ [FromQuery] string? 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] string? excludeItemIds,
+ [FromQuery] int? startIndex,
+ [FromQuery] int? limit,
+ [FromQuery] bool? recursive,
+ [FromQuery] string? searchTerm,
+ [FromQuery] string? sortOrder,
+ [FromQuery] string? parentId,
+ [FromQuery] string? fields,
+ [FromQuery] string? excludeItemTypes,
+ [FromQuery] string? filters,
+ [FromQuery] bool? isFavorite,
+ [FromQuery] string? mediaTypes,
+ [FromQuery] string? imageTypes,
+ [FromQuery] string? sortBy,
+ [FromQuery] bool? isPlayed,
+ [FromQuery] string? genres,
+ [FromQuery] string? officialRatings,
+ [FromQuery] string? tags,
+ [FromQuery] string? years,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] int? imageTypeLimit,
+ [FromQuery] string? enableImageTypes,
+ [FromQuery] string? person,
+ [FromQuery] string? personIds,
+ [FromQuery] string? personTypes,
+ [FromQuery] string? studios,
+ [FromQuery] string? artists,
+ [FromQuery] string? excludeArtistIds,
+ [FromQuery] string? artistIds,
+ [FromQuery] string? albumArtistIds,
+ [FromQuery] string? contributingArtistIds,
+ [FromQuery] string? albums,
+ [FromQuery] string? albumIds,
+ [FromQuery] string? ids,
+ [FromQuery] string? 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] string? seriesStatus,
+ [FromQuery] string? nameStartsWithOrGreater,
+ [FromQuery] string? nameStartsWith,
+ [FromQuery] string? nameLessThan,
+ [FromQuery] string? studioIds,
+ [FromQuery] string? genreIds,
+ [FromQuery] bool enableTotalRecordCount = true,
+ [FromQuery] bool? enableImages = true)
+ {
+ var includeItemTypes = "Trailer";
+
+ return new ItemsController(
+ _userManager,
+ _libraryManager,
+ _localizationManager,
+ _dtoService,
+ _logger)
+ .GetItems(
+ userId,
+ userId,
+ maxOfficialRating,
+ hasThemeSong,
+ hasThemeVideo,
+ hasSubtitles,
+ hasSpecialFeature,
+ hasTrailer,
+ adjacentTo,
+ parentIndexNumber,
+ hasParentalRating,
+ isHd,
+ is4K,
+ locationTypes,
+ excludeLocationTypes,
+ isMissing,
+ isUnaired,
+ minCommunityRating,
+ minCriticRating,
+ minPremiereDate,
+ minDateLastSaved,
+ minDateLastSavedForUser,
+ maxPremiereDate,
+ hasOverview,
+ hasImdbId,
+ hasTmdbId,
+ hasTvdbId,
+ 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/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs
index 6738dd8c8..d54bc10c0 100644
--- a/Jellyfin.Api/Controllers/TvShowsController.cs
+++ b/Jellyfin.Api/Controllers/TvShowsController.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using Jellyfin.Api.Constants;
@@ -69,7 +68,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("NextUp")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetNextUp(
- [FromQuery] Guid userId,
+ [FromQuery] Guid? userId,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] string? fields,
@@ -93,12 +92,14 @@ namespace Jellyfin.Api.Controllers
ParentId = parentId,
SeriesId = seriesId,
StartIndex = startIndex,
- UserId = userId,
+ UserId = userId ?? Guid.Empty,
EnableTotalRecordCount = enableTotalRecordCount
},
options);
- var user = _userManager.GetUserById(userId);
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
var returnItems = _dtoService.GetBaseItemDtos(result.Items, options, user);
@@ -125,7 +126,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Upcoming")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetUpcomingEpisodes(
- [FromQuery] Guid userId,
+ [FromQuery] Guid? userId,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] string? fields,
@@ -135,7 +136,9 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? enableImageTypes,
[FromQuery] bool? enableUserData)
{
- var user = _userManager.GetUserById(userId);
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
var minPremiereDate = DateTime.Now.Date.ToUniversalTime().AddDays(-1);
@@ -190,8 +193,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<QueryResult<BaseItemDto>> GetEpisodes(
- [FromRoute] string seriesId,
- [FromQuery] Guid userId,
+ [FromRoute] string? seriesId,
+ [FromQuery] Guid? userId,
[FromQuery] string? fields,
[FromQuery] int? season,
[FromQuery] string? seasonId,
@@ -206,7 +209,9 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? enableUserData,
[FromQuery] string? sortBy)
{
- var user = _userManager.GetUserById(userId);
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
List<BaseItem> episodes;
@@ -311,18 +316,20 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<QueryResult<BaseItemDto>> GetSeasons(
- [FromRoute] string seriesId,
- [FromQuery] Guid userId,
- [FromQuery] string fields,
+ [FromRoute] string? seriesId,
+ [FromQuery] Guid? userId,
+ [FromQuery] string? fields,
[FromQuery] bool? isSpecialSeason,
[FromQuery] bool? isMissing,
- [FromQuery] string adjacentTo,
+ [FromQuery] string? adjacentTo,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
[FromQuery] string? enableImageTypes,
[FromQuery] bool? enableUserData)
{
- var user = _userManager.GetUserById(userId);
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
if (!(_libraryManager.GetItemById(seriesId) is Series series))
{
diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs
index 9f8d564a7..8038ca044 100644
--- a/Jellyfin.Api/Controllers/UserController.cs
+++ b/Jellyfin.Api/Controllers/UserController.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
-using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
@@ -137,14 +136,8 @@ namespace Jellyfin.Api.Controllers
public ActionResult DeleteUser([FromRoute] Guid userId)
{
var user = _userManager.GetUserById(userId);
-
- if (user == null)
- {
- return NotFound("User not found");
- }
-
_sessionManager.RevokeUserTokens(user.Id, null);
- _userManager.DeleteUser(user);
+ _userManager.DeleteUser(userId);
return NoContent();
}
@@ -164,8 +157,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<AuthenticationResult>> AuthenticateUser(
[FromRoute, Required] Guid userId,
- [FromQuery, BindRequired] string pw,
- [FromQuery, BindRequired] string password)
+ [FromQuery, BindRequired] string? pw,
+ [FromQuery, BindRequired] string? password)
{
var user = _userManager.GetUserById(userId);
@@ -483,7 +476,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="Task"/> containing a <see cref="ForgotPasswordResult"/>.</returns>
[HttpPost("ForgotPassword")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public async Task<ActionResult<ForgotPasswordResult>> ForgotPassword([FromBody] string enteredUsername)
+ public async Task<ActionResult<ForgotPasswordResult>> ForgotPassword([FromBody] string? enteredUsername)
{
var isLocal = HttpContext.Connection.RemoteIpAddress.Equals(HttpContext.Connection.LocalIpAddress)
|| _networkManager.IsInLocalNetwork(HttpContext.Connection.RemoteIpAddress.ToString());
@@ -501,7 +494,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="Task"/> containing a <see cref="PinRedeemResult"/>.</returns>
[HttpPost("ForgotPassword/Pin")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public async Task<ActionResult<PinRedeemResult>> ForgotPasswordPin([FromBody] string pin)
+ public async Task<ActionResult<PinRedeemResult>> ForgotPasswordPin([FromBody] string? pin)
{
var result = await _userManager.RedeemPasswordResetPin(pin).ConfigureAwait(false);
return result;
diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs
new file mode 100644
index 000000000..cedda3b9d
--- /dev/null
+++ b/Jellyfin.Api/Controllers/UserLibraryController.cs
@@ -0,0 +1,391 @@
+using System;
+using System.Collections.Generic;
+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.Extensions;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+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>
+ [Authorize(Policy = Policies.DefaultAuthorization)]
+ 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 d item.</returns>
+ [HttpGet("/Users/{userId}/Items/{itemId}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task<ActionResult<BaseItemDto>> GetItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
+ {
+ var user = _userManager.GetUserById(userId);
+
+ var item = itemId.Equals(Guid.Empty)
+ ? _libraryManager.GetUserRootFolder()
+ : _libraryManager.GetItemById(itemId);
+
+ await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false);
+
+ var dtoOptions = new DtoOptions().AddClientFields(Request);
+
+ 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)]
+ public ActionResult<BaseItemDto> GetRootFolder([FromRoute] Guid userId)
+ {
+ var user = _userManager.GetUserById(userId);
+ var item = _libraryManager.GetUserRootFolder();
+ var dtoOptions = new DtoOptions().AddClientFields(Request);
+ return _dtoService.GetBaseItemDto(item, dtoOptions, user);
+ }
+
+ /// <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)]
+ public async Task<ActionResult<QueryResult<BaseItemDto>>> GetIntros([FromRoute] Guid userId, [FromRoute] Guid itemId)
+ {
+ var user = _userManager.GetUserById(userId);
+
+ var item = itemId.Equals(Guid.Empty)
+ ? _libraryManager.GetUserRootFolder()
+ : _libraryManager.GetItemById(itemId);
+
+ var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false);
+ var dtoOptions = new DtoOptions().AddClientFields(Request);
+ var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray();
+
+ return new QueryResult<BaseItemDto>
+ {
+ Items = dtos,
+ TotalRecordCount = dtos.Length
+ };
+ }
+
+ /// <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)]
+ public ActionResult<UserItemDataDto> MarkFavoriteItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
+ {
+ return MarkFavorite(userId, itemId, true);
+ }
+
+ /// <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)]
+ public ActionResult<UserItemDataDto> UnmarkFavoriteItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
+ {
+ return MarkFavorite(userId, itemId, false);
+ }
+
+ /// <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)]
+ public ActionResult<UserItemDataDto> DeleteUserItemRating([FromRoute] Guid userId, [FromRoute] Guid itemId)
+ {
+ return UpdateUserItemRatingInternal(userId, itemId, null);
+ }
+
+ /// <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)]
+ public ActionResult<UserItemDataDto> UpdateUserItemRating([FromRoute] Guid userId, [FromRoute] Guid itemId, [FromQuery] bool? likes)
+ {
+ return UpdateUserItemRatingInternal(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("/Users/{userId}/Items/{itemId}/LocalTrailers")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult<IEnumerable<BaseItemDto>> GetLocalTrailers([FromRoute] Guid userId, [FromRoute] Guid itemId)
+ {
+ var user = _userManager.GetUserById(userId);
+
+ var item = itemId.Equals(Guid.Empty)
+ ? _libraryManager.GetUserRootFolder()
+ : _libraryManager.GetItemById(itemId);
+
+ var dtoOptions = new DtoOptions().AddClientFields(Request);
+ var dtosExtras = item.GetExtras(new[] { ExtraType.Trailer })
+ .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
+ .ToArray();
+
+ if (item is IHasTrailers hasTrailers)
+ {
+ var trailers = hasTrailers.GetTrailers();
+ var dtosTrailers = _dtoService.GetBaseItemDtos(trailers, dtoOptions, user, item);
+ var allTrailers = new BaseItemDto[dtosExtras.Length + dtosTrailers.Count];
+ dtosExtras.CopyTo(allTrailers, 0);
+ dtosTrailers.CopyTo(allTrailers, dtosExtras.Length);
+ return allTrailers;
+ }
+
+ return dtosExtras;
+ }
+
+ /// <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)]
+ public ActionResult<IEnumerable<BaseItemDto>> GetSpecialFeatures([FromRoute] Guid userId, [FromRoute] Guid itemId)
+ {
+ var user = _userManager.GetUserById(userId);
+
+ var item = itemId.Equals(Guid.Empty)
+ ? _libraryManager.GetUserRootFolder()
+ : _libraryManager.GetItemById(itemId);
+
+ var dtoOptions = new DtoOptions().AddClientFields(Request);
+
+ return Ok(item
+ .GetExtras(BaseItem.DisplayExtraTypes)
+ .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)));
+ }
+
+ /// <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. This allows multiple, comma delimeted. Options: Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, SortName, Studios, Taglines.</param>
+ /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.</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)]
+ public ActionResult<IEnumerable<BaseItemDto>> GetLatestMedia(
+ [FromRoute] Guid userId,
+ [FromQuery] Guid? parentId,
+ [FromQuery] string? fields,
+ [FromQuery] string? includeItemTypes,
+ [FromQuery] bool? isPlayed,
+ [FromQuery] bool? enableImages,
+ [FromQuery] int? imageTypeLimit,
+ [FromQuery] string? enableImageTypes,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] int limit = 20,
+ [FromQuery] bool groupItems = true)
+ {
+ var user = _userManager.GetUserById(userId);
+
+ if (!isPlayed.HasValue)
+ {
+ if (user.HidePlayedInLatest)
+ {
+ isPlayed = false;
+ }
+ }
+
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request)
+ .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
+
+ var list = _userViewManager.GetLatestItems(
+ new LatestItemsQuery
+ {
+ GroupItems = groupItems,
+ IncludeItemTypes = RequestHelpers.Split(includeItemTypes, ',', true),
+ IsPlayed = isPlayed,
+ Limit = limit,
+ ParentId = parentId ?? Guid.Empty,
+ UserId = userId,
+ }, dtoOptions);
+
+ var dtos = list.Select(i =>
+ {
+ var item = i.Item2[0];
+ var childCount = 0;
+
+ if (i.Item1 != null && (i.Item2.Count > 1 || i.Item1 is MusicAlbum))
+ {
+ item = i.Item1;
+ childCount = i.Item2.Count;
+ }
+
+ var dto = _dtoService.GetBaseItemDto(item, dtoOptions, user);
+
+ dto.ChildCount = childCount;
+
+ return dto;
+ });
+
+ return Ok(dtos);
+ }
+
+ private async Task RefreshItemOnDemandIfNeeded(BaseItem item)
+ {
+ if (item is Person)
+ {
+ var hasMetdata = !string.IsNullOrWhiteSpace(item.Overview) && item.HasImage(ImageType.Primary);
+ var performFullRefresh = !hasMetdata && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= 3;
+
+ if (!hasMetdata)
+ {
+ 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="userId">The user id.</param>
+ /// <param name="itemId">The item id.</param>
+ /// <param name="isFavorite">if set to <c>true</c> [is favorite].</param>
+ private UserItemDataDto MarkFavorite(Guid userId, Guid itemId, bool isFavorite)
+ {
+ var user = _userManager.GetUserById(userId);
+
+ var item = itemId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId);
+
+ // Get the user data for this item
+ var data = _userDataRepository.GetUserData(user, item);
+
+ // 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="userId">The user id.</param>
+ /// <param name="itemId">The item id.</param>
+ /// <param name="likes">if set to <c>true</c> [likes].</param>
+ private UserItemDataDto UpdateUserItemRatingInternal(Guid userId, Guid itemId, bool? likes)
+ {
+ var user = _userManager.GetUserById(userId);
+
+ var item = itemId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId);
+
+ // Get the user data for this item
+ var data = _userDataRepository.GetUserData(user, item);
+
+ 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 000000000..f4bd451ef
--- /dev/null
+++ b/Jellyfin.Api/Controllers/UserViewsController.cs
@@ -0,0 +1,148 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
+using Jellyfin.Api.Models.UserViewDtos;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Library;
+using MediaBrowser.Model.Querying;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Jellyfin.Api.Controllers
+{
+ /// <summary>
+ /// User views controller.
+ /// </summary>
+ public class UserViewsController : BaseJellyfinApiController
+ {
+ private readonly IUserManager _userManager;
+ private readonly IUserViewManager _userViewManager;
+ private readonly IDtoService _dtoService;
+ private readonly IAuthorizationContext _authContext;
+ 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="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
+ /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
+ public UserViewsController(
+ IUserManager userManager,
+ IUserViewManager userViewManager,
+ IDtoService dtoService,
+ IAuthorizationContext authContext,
+ ILibraryManager libraryManager)
+ {
+ _userManager = userManager;
+ _userViewManager = userViewManager;
+ _dtoService = dtoService;
+ _authContext = authContext;
+ _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("/Users/{userId}/Views")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult<QueryResult<BaseItemDto>> GetUserViews(
+ [FromRoute] Guid userId,
+ [FromQuery] bool? includeExternalContent,
+ [FromQuery] string? presetViews,
+ [FromQuery] bool includeHidden = false)
+ {
+ var query = new UserViewQuery
+ {
+ UserId = userId,
+ IncludeHidden = includeHidden
+ };
+
+ if (includeExternalContent.HasValue)
+ {
+ query.IncludeExternalContent = includeExternalContent.Value;
+ }
+
+ if (!string.IsNullOrWhiteSpace(presetViews))
+ {
+ query.PresetViews = RequestHelpers.Split(presetViews, ',', true);
+ }
+
+ var app = _authContext.GetAuthorizationInfo(Request).Client ?? string.Empty;
+ if (app.IndexOf("emby rt", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ query.PresetViews = new[] { CollectionType.Movies, CollectionType.TvShows };
+ }
+
+ var folders = _userViewManager.GetUserViews(query);
+
+ var dtoOptions = new DtoOptions().AddClientFields(Request);
+ var fields = dtoOptions.Fields.ToList();
+
+ fields.Add(ItemFields.PrimaryImageAspectRatio);
+ fields.Add(ItemFields.DisplayPreferencesId);
+ fields.Remove(ItemFields.BasicSyncInfo);
+ dtoOptions.Fields = fields.ToArray();
+
+ var user = _userManager.GetUserById(userId);
+
+ var dtos = folders.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user))
+ .ToArray();
+
+ return new QueryResult<BaseItemDto>
+ {
+ Items = dtos,
+ TotalRecordCount = dtos.Length
+ };
+ }
+
+ /// <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)]
+ public ActionResult<IEnumerable<SpecialViewOptionDto>> GetGroupingOptions([FromRoute] Guid userId)
+ {
+ var user = _userManager.GetUserById(userId);
+ if (user == 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));
+ }
+ }
+}
diff --git a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs
index 943ba8af3..eef0a93cd 100644
--- a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs
+++ b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs
@@ -50,7 +50,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<FileStreamResult>> GetAttachment(
[FromRoute] Guid videoId,
- [FromRoute] string mediaSourceId,
+ [FromRoute] string? mediaSourceId,
[FromRoute] int index)
{
try
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index effe630a9..e2a44427b 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -53,9 +53,11 @@ namespace Jellyfin.Api.Controllers
[HttpGet("{itemId}/AdditionalParts")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<QueryResult<BaseItemDto>> GetAdditionalPart([FromRoute] Guid itemId, [FromQuery] Guid userId)
+ public ActionResult<QueryResult<BaseItemDto>> GetAdditionalPart([FromRoute] Guid itemId, [FromQuery] Guid? userId)
{
- var user = !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId) : null;
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
var item = itemId.Equals(Guid.Empty)
? (!userId.Equals(Guid.Empty)
@@ -133,7 +135,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
- public ActionResult MergeVersions([FromQuery] string itemIds)
+ public ActionResult MergeVersions([FromQuery] string? itemIds)
{
var items = RequestHelpers.Split(itemIds, ',', true)
.Select(i => _libraryManager.GetItemById(i))
diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs
new file mode 100644
index 000000000..d09b016a9
--- /dev/null
+++ b/Jellyfin.Api/Controllers/YearsController.cs
@@ -0,0 +1,231 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
+using Jellyfin.Data.Entities;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Querying;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Jellyfin.Api.Controllers
+{
+ /// <summary>
+ /// Years controller.
+ /// </summary>
+ 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. 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 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] string? sortOrder,
+ [FromQuery] string? parentId,
+ [FromQuery] string? fields,
+ [FromQuery] string? excludeItemTypes,
+ [FromQuery] string? includeItemTypes,
+ [FromQuery] string? mediaTypes,
+ [FromQuery] string? sortBy,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] int? imageTypeLimit,
+ [FromQuery] string? enableImageTypes,
+ [FromQuery] Guid? userId,
+ [FromQuery] bool recursive = true,
+ [FromQuery] bool? enableImages = true)
+ {
+ var dtoOptions = new DtoOptions()
+ .AddItemFields(fields)
+ .AddClientFields(Request)
+ .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
+
+ User? user = null;
+ BaseItem parentItem;
+
+ if (userId.HasValue && !userId.Equals(Guid.Empty))
+ {
+ user = _userManager.GetUserById(userId.Value);
+ parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
+ }
+ else
+ {
+ parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId);
+ }
+
+ IList<BaseItem> items;
+
+ var excludeItemTypesArr = RequestHelpers.Split(excludeItemTypes, ',', true);
+ var includeItemTypesArr = RequestHelpers.Split(includeItemTypes, ',', true);
+ var mediaTypesArr = RequestHelpers.Split(mediaTypes, ',', true);
+
+ var query = new InternalItemsQuery(user)
+ {
+ ExcludeItemTypes = excludeItemTypesArr,
+ IncludeItemTypes = includeItemTypesArr,
+ MediaTypes = mediaTypesArr,
+ DtoOptions = dtoOptions
+ };
+
+ bool Filter(BaseItem i) => FilterItem(i, excludeItemTypesArr, includeItemTypesArr, mediaTypesArr);
+
+ if (parentItem.IsFolder)
+ {
+ var folder = (Folder)parentItem;
+
+ if (!userId.Equals(Guid.Empty))
+ {
+ items = recursive ? folder.GetRecursiveChildren(user, query).ToList() : folder.GetChildren(user, true).Where(Filter).ToList();
+ }
+ else
+ {
+ items = recursive ? folder.GetRecursiveChildren(Filter) : folder.Children.Where(Filter).ToList();
+ }
+ }
+ 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;
+
+ var result = new QueryResult<BaseItemDto> { TotalRecordCount = ibnItemsArray.Count };
+
+ 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));
+
+ result.Items = dtos.Where(i => i != 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] int year, [FromQuery] Guid? userId)
+ {
+ var item = _libraryManager.GetYear(year);
+ if (item == null)
+ {
+ return NotFound();
+ }
+
+ var dtoOptions = new DtoOptions()
+ .AddClientFields(Request);
+
+ if (userId.HasValue && !userId.Equals(Guid.Empty))
+ {
+ var user = _userManager.GetUserById(userId.Value);
+ return _dtoService.GetBaseItemDto(item, dtoOptions, user);
+ }
+
+ return _dtoService.GetBaseItemDto(item, dtoOptions);
+ }
+
+ private bool FilterItem(BaseItem f, IReadOnlyCollection<string> excludeItemTypes, IReadOnlyCollection<string> includeItemTypes, IReadOnlyCollection<string> mediaTypes)
+ {
+ // Exclude item types
+ if (excludeItemTypes.Count > 0 && excludeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ // Include item types
+ if (includeItemTypes.Count > 0 && !includeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ // Include MediaTypes
+ if (mediaTypes.Count > 0 && !mediaTypes.Contains(f.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private IEnumerable<BaseItem> GetAllItems(IEnumerable<BaseItem> items)
+ {
+ return items
+ .Select(i => i.ProductionYear ?? 0)
+ .Where(i => i > 0)
+ .Distinct()
+ .Select(year => _libraryManager.GetYear(year));
+ }
+ }
+}
diff --git a/Jellyfin.Api/Extensions/DtoExtensions.cs b/Jellyfin.Api/Extensions/DtoExtensions.cs
index ac248109d..e61e9c29d 100644
--- a/Jellyfin.Api/Extensions/DtoExtensions.cs
+++ b/Jellyfin.Api/Extensions/DtoExtensions.cs
@@ -23,7 +23,7 @@ namespace Jellyfin.Api.Extensions
/// <param name="dtoOptions">DtoOptions object.</param>
/// <param name="fields">Comma delimited string of fields.</param>
/// <returns>Modified DtoOptions object.</returns>
- internal static DtoOptions AddItemFields(this DtoOptions dtoOptions, string fields)
+ internal static DtoOptions AddItemFields(this DtoOptions dtoOptions, string? fields)
{
if (string.IsNullOrEmpty(fields))
{
@@ -126,7 +126,7 @@ namespace Jellyfin.Api.Extensions
bool? enableImages,
bool? enableUserData,
int? imageTypeLimit,
- string enableImageTypes)
+ string? enableImageTypes)
{
dtoOptions.EnableImages = enableImages ?? true;
diff --git a/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs b/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs
new file mode 100644
index 000000000..e8e6966f4
--- /dev/null
+++ b/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs
@@ -0,0 +1,84 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.IO;
+
+namespace Jellyfin.Api.Helpers
+{
+ /// <summary>
+ /// Progressive file copier.
+ /// </summary>
+ public class ProgressiveFileCopier
+ {
+ private readonly string? _path;
+ private readonly IDirectStreamProvider? _directStreamProvider;
+ private readonly IStreamHelper _streamHelper;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ProgressiveFileCopier"/> class.
+ /// </summary>
+ /// <param name="streamHelper">Instance of the <see cref="IStreamHelper"/> interface.</param>
+ /// <param name="path">Filepath to stream from.</param>
+ public ProgressiveFileCopier(IStreamHelper streamHelper, string path)
+ {
+ _path = path;
+ _streamHelper = streamHelper;
+ _directStreamProvider = null;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ProgressiveFileCopier"/> class.
+ /// </summary>
+ /// <param name="streamHelper">Instance of the <see cref="IStreamHelper"/> interface.</param>
+ /// <param name="directStreamProvider">Instance of the <see cref="IDirectStreamProvider"/> interface.</param>
+ public ProgressiveFileCopier(IStreamHelper streamHelper, IDirectStreamProvider directStreamProvider)
+ {
+ _directStreamProvider = directStreamProvider;
+ _streamHelper = streamHelper;
+ _path = null;
+ }
+
+ /// <summary>
+ /// Write source stream to output.
+ /// </summary>
+ /// <param name="outputStream">Output stream.</param>
+ /// <param name="cancellationToken">Cancellation token.</param>
+ /// <returns>A <see cref="Task"/>.</returns>
+ public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken)
+ {
+ if (_directStreamProvider != null)
+ {
+ await _directStreamProvider.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false);
+ return;
+ }
+
+ var fileOptions = FileOptions.SequentialScan;
+
+ // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
+ if (Environment.OSVersion.Platform != PlatformID.Win32NT)
+ {
+ fileOptions |= FileOptions.Asynchronous;
+ }
+
+ await using var inputStream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, fileOptions);
+ const int emptyReadLimit = 100;
+ var eofCount = 0;
+ while (eofCount < emptyReadLimit)
+ {
+ var bytesRead = await _streamHelper.CopyToAsync(inputStream, outputStream, cancellationToken).ConfigureAwait(false);
+
+ if (bytesRead == 0)
+ {
+ eofCount++;
+ await Task.Delay(100, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ eofCount = 0;
+ }
+ }
+ }
+ }
+}
diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs
index 446ee716a..299c7d4aa 100644
--- a/Jellyfin.Api/Helpers/RequestHelpers.cs
+++ b/Jellyfin.Api/Helpers/RequestHelpers.cs
@@ -1,8 +1,11 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Http;
namespace Jellyfin.Api.Helpers
@@ -13,13 +16,65 @@ namespace Jellyfin.Api.Helpers
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 ValueTuple<string, SortOrder>[] GetOrderBy(string? sortBy, string? requestedSortOrder)
+ {
+ var val = sortBy;
+
+ if (string.IsNullOrEmpty(val))
+ {
+ return Array.Empty<ValueTuple<string, SortOrder>>();
+ }
+
+ var vals = val.Split(',');
+ if (string.IsNullOrWhiteSpace(requestedSortOrder))
+ {
+ requestedSortOrder = "Ascending";
+ }
+
+ var sortOrders = requestedSortOrder.Split(',');
+
+ var result = new ValueTuple<string, SortOrder>[vals.Length];
+
+ for (var i = 0; i < vals.Length; i++)
+ {
+ var sortOrderIndex = sortOrders.Length > i ? i : 0;
+
+ var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null;
+ var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase)
+ ? SortOrder.Descending
+ : SortOrder.Ascending;
+
+ result[i] = new ValueTuple<string, SortOrder>(vals[i], sortOrder);
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Get parsed filters.
+ /// </summary>
+ /// <param name="filters">The filters.</param>
+ /// <returns>Item filters.</returns>
+ public static IEnumerable<ItemFilter> GetFilters(string? filters)
+ {
+ return string.IsNullOrEmpty(filters)
+ ? Array.Empty<ItemFilter>()
+ : filters.Split(',').Select(v => Enum.Parse<ItemFilter>(v, true));
+ }
+
+ /// <summary>
/// Splits a string at a separating character into an array of substrings.
/// </summary>
/// <param name="value">The string to split.</param>
/// <param name="separator">The char that separates the substrings.</param>
/// <param name="removeEmpty">Option to remove empty substrings from the array.</param>
/// <returns>An array of the substrings.</returns>
- internal static string[] Split(string value, char separator, bool removeEmpty)
+ internal static string[] Split(string? value, char separator, bool removeEmpty)
{
if (string.IsNullOrWhiteSpace(value))
{
@@ -91,5 +146,31 @@ namespace Jellyfin.Api.Helpers
.Select(i => new Guid(i))
.ToArray();
}
+
+ /// <summary>
+ /// Gets the item fields.
+ /// </summary>
+ /// <param name="fields">The fields string.</param>
+ /// <returns>IEnumerable{ItemFields}.</returns>
+ internal static ItemFields[] GetItemFields(string? fields)
+ {
+ if (string.IsNullOrEmpty(fields))
+ {
+ return Array.Empty<ItemFields>();
+ }
+
+ return Split(fields, ',', true)
+ .Select(v =>
+ {
+ if (Enum.TryParse(v, true, out ItemFields value))
+ {
+ return (ItemFields?)value;
+ }
+
+ return null;
+ }).Where(i => i.HasValue)
+ .Select(i => i!.Value)
+ .ToArray();
+ }
}
}
diff --git a/Jellyfin.Api/Helpers/SimilarItemsHelper.cs b/Jellyfin.Api/Helpers/SimilarItemsHelper.cs
index 751e3c481..b922e76cf 100644
--- a/Jellyfin.Api/Helpers/SimilarItemsHelper.cs
+++ b/Jellyfin.Api/Helpers/SimilarItemsHelper.cs
@@ -4,7 +4,6 @@ using System.Linq;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
@@ -21,14 +20,16 @@ namespace Jellyfin.Api.Helpers
IUserManager userManager,
ILibraryManager libraryManager,
IDtoService dtoService,
- Guid userId,
+ Guid? userId,
string id,
- string excludeArtistIds,
+ string? excludeArtistIds,
int? limit,
Type[] includeTypes,
Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore)
{
- var user = !userId.Equals(Guid.Empty) ? userManager.GetUserById(userId) : null;
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? userManager.GetUserById(userId.Value)
+ : null;
var item = string.IsNullOrEmpty(id) ?
(!userId.Equals(Guid.Empty) ? libraryManager.GetUserRootFolder() :
@@ -38,11 +39,10 @@ namespace Jellyfin.Api.Helpers
{
IncludeItemTypes = includeTypes.Select(i => i.Name).ToArray(),
Recursive = true,
- DtoOptions = dtoOptions
+ DtoOptions = dtoOptions,
+ ExcludeArtistIds = RequestHelpers.GetGuids(excludeArtistIds)
};
- query.ExcludeArtistIds = RequestHelpers.GetGuids(excludeArtistIds);
-
var inputItems = libraryManager.GetItemList(query);
var items = GetSimilaritems(item, libraryManager, inputItems, getSimilarityScore)
diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
new file mode 100644
index 000000000..34b371d3f
--- /dev/null
+++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
@@ -0,0 +1,353 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Jellyfin.Api.Models.PlaybackDtos;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.IO;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Api.Helpers
+{
+ /// <summary>
+ /// Transcoding job helpers.
+ /// </summary>
+ public class TranscodingJobHelper
+ {
+ /// <summary>
+ /// The active transcoding jobs.
+ /// </summary>
+ private static readonly List<TranscodingJobDto> _activeTranscodingJobs = new List<TranscodingJobDto>();
+
+ /// <summary>
+ /// The transcoding locks.
+ /// </summary>
+ private static readonly Dictionary<string, SemaphoreSlim> _transcodingLocks = new Dictionary<string, SemaphoreSlim>();
+
+ private readonly ILogger<TranscodingJobHelper> _logger;
+ private readonly IMediaSourceManager _mediaSourceManager;
+ private readonly IFileSystem _fileSystem;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TranscodingJobHelper"/> class.
+ /// </summary>
+ /// <param name="logger">Instance of the <see cref="ILogger{TranscodingJobHelpers}"/> interface.</param>
+ /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
+ /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+ public TranscodingJobHelper(
+ ILogger<TranscodingJobHelper> logger,
+ IMediaSourceManager mediaSourceManager,
+ IFileSystem fileSystem)
+ {
+ _logger = logger;
+ _mediaSourceManager = mediaSourceManager;
+ _fileSystem = fileSystem;
+ }
+
+ /// <summary>
+ /// Get transcoding job.
+ /// </summary>
+ /// <param name="playSessionId">Playback session id.</param>
+ /// <returns>The transcoding job.</returns>
+ public TranscodingJobDto GetTranscodingJob(string playSessionId)
+ {
+ lock (_activeTranscodingJobs)
+ {
+ return _activeTranscodingJobs.FirstOrDefault(j => string.Equals(j.PlaySessionId, playSessionId, StringComparison.OrdinalIgnoreCase));
+ }
+ }
+
+ /// <summary>
+ /// Ping transcoding job.
+ /// </summary>
+ /// <param name="playSessionId">Play session id.</param>
+ /// <param name="isUserPaused">Is user paused.</param>
+ /// <exception cref="ArgumentNullException">Play session id is null.</exception>
+ public void PingTranscodingJob(string playSessionId, bool? isUserPaused)
+ {
+ if (string.IsNullOrEmpty(playSessionId))
+ {
+ throw new ArgumentNullException(nameof(playSessionId));
+ }
+
+ _logger.LogDebug("PingTranscodingJob PlaySessionId={0} isUsedPaused: {1}", playSessionId, isUserPaused);
+
+ List<TranscodingJobDto> jobs;
+
+ lock (_activeTranscodingJobs)
+ {
+ // This is really only needed for HLS.
+ // Progressive streams can stop on their own reliably
+ jobs = _activeTranscodingJobs.Where(j => string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase)).ToList();
+ }
+
+ foreach (var job in jobs)
+ {
+ if (isUserPaused.HasValue)
+ {
+ _logger.LogDebug("Setting job.IsUserPaused to {0}. jobId: {1}", isUserPaused, job.Id);
+ job.IsUserPaused = isUserPaused.Value;
+ }
+
+ PingTimer(job, true);
+ }
+ }
+
+ private void PingTimer(TranscodingJobDto job, bool isProgressCheckIn)
+ {
+ if (job.HasExited)
+ {
+ job.StopKillTimer();
+ return;
+ }
+
+ var timerDuration = 10000;
+
+ if (job.Type != TranscodingJobType.Progressive)
+ {
+ timerDuration = 60000;
+ }
+
+ job.PingTimeout = timerDuration;
+ job.LastPingDate = DateTime.UtcNow;
+
+ // Don't start the timer for playback checkins with progressive streaming
+ if (job.Type != TranscodingJobType.Progressive || !isProgressCheckIn)
+ {
+ job.StartKillTimer(OnTranscodeKillTimerStopped);
+ }
+ else
+ {
+ job.ChangeKillTimerIfStarted();
+ }
+ }
+
+ /// <summary>
+ /// Called when [transcode kill timer stopped].
+ /// </summary>
+ /// <param name="state">The state.</param>
+ private async void OnTranscodeKillTimerStopped(object state)
+ {
+ var job = (TranscodingJobDto)state;
+
+ if (!job.HasExited && job.Type != TranscodingJobType.Progressive)
+ {
+ var timeSinceLastPing = (DateTime.UtcNow - job.LastPingDate).TotalMilliseconds;
+
+ if (timeSinceLastPing < job.PingTimeout)
+ {
+ job.StartKillTimer(OnTranscodeKillTimerStopped, job.PingTimeout);
+ return;
+ }
+ }
+
+ _logger.LogInformation("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId);
+
+ await KillTranscodingJob(job, true, path => true).ConfigureAwait(false);
+ }
+
+ /// <summary>
+ /// Kills the single transcoding job.
+ /// </summary>
+ /// <param name="deviceId">The device id.</param>
+ /// <param name="playSessionId">The play session identifier.</param>
+ /// <param name="deleteFiles">The delete files.</param>
+ /// <returns>Task.</returns>
+ public Task KillTranscodingJobs(string deviceId, string playSessionId, Func<string, bool> deleteFiles)
+ {
+ return KillTranscodingJobs(
+ j => string.IsNullOrWhiteSpace(playSessionId)
+ ? string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase)
+ : string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase), deleteFiles);
+ }
+
+ /// <summary>
+ /// Kills the transcoding jobs.
+ /// </summary>
+ /// <param name="killJob">The kill job.</param>
+ /// <param name="deleteFiles">The delete files.</param>
+ /// <returns>Task.</returns>
+ private Task KillTranscodingJobs(Func<TranscodingJobDto, bool> killJob, Func<string, bool> deleteFiles)
+ {
+ var jobs = new List<TranscodingJobDto>();
+
+ lock (_activeTranscodingJobs)
+ {
+ // This is really only needed for HLS.
+ // Progressive streams can stop on their own reliably
+ jobs.AddRange(_activeTranscodingJobs.Where(killJob));
+ }
+
+ if (jobs.Count == 0)
+ {
+ return Task.CompletedTask;
+ }
+
+ IEnumerable<Task> GetKillJobs()
+ {
+ foreach (var job in jobs)
+ {
+ yield return KillTranscodingJob(job, false, deleteFiles);
+ }
+ }
+
+ return Task.WhenAll(GetKillJobs());
+ }
+
+ /// <summary>
+ /// Kills the transcoding job.
+ /// </summary>
+ /// <param name="job">The job.</param>
+ /// <param name="closeLiveStream">if set to <c>true</c> [close live stream].</param>
+ /// <param name="delete">The delete.</param>
+ private async Task KillTranscodingJob(TranscodingJobDto job, bool closeLiveStream, Func<string, bool> delete)
+ {
+ job.DisposeKillTimer();
+
+ _logger.LogDebug("KillTranscodingJob - JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId);
+
+ lock (_activeTranscodingJobs)
+ {
+ _activeTranscodingJobs.Remove(job);
+
+ if (!job.CancellationTokenSource!.IsCancellationRequested)
+ {
+ job.CancellationTokenSource.Cancel();
+ }
+ }
+
+ lock (_transcodingLocks)
+ {
+ _transcodingLocks.Remove(job.Path!);
+ }
+
+ lock (job.ProcessLock!)
+ {
+ job.TranscodingThrottler?.Stop().GetAwaiter().GetResult();
+
+ var process = job.Process;
+
+ var hasExited = job.HasExited;
+
+ if (!hasExited)
+ {
+ try
+ {
+ _logger.LogInformation("Stopping ffmpeg process with q command for {Path}", job.Path);
+
+ process!.StandardInput.WriteLine("q");
+
+ // Need to wait because killing is asynchronous
+ if (!process.WaitForExit(5000))
+ {
+ _logger.LogInformation("Killing ffmpeg process for {Path}", job.Path);
+ process.Kill();
+ }
+ }
+ catch (InvalidOperationException)
+ {
+ }
+ }
+ }
+
+ if (delete(job.Path!))
+ {
+ await DeletePartialStreamFiles(job.Path!, job.Type, 0, 1500).ConfigureAwait(false);
+ }
+
+ if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId))
+ {
+ try
+ {
+ await _mediaSourceManager.CloseLiveStream(job.LiveStreamId).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error closing live stream for {Path}", job.Path);
+ }
+ }
+ }
+
+ private async Task DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs)
+ {
+ if (retryCount >= 10)
+ {
+ return;
+ }
+
+ _logger.LogInformation("Deleting partial stream file(s) {Path}", path);
+
+ await Task.Delay(delayMs).ConfigureAwait(false);
+
+ try
+ {
+ if (jobType == TranscodingJobType.Progressive)
+ {
+ DeleteProgressivePartialStreamFiles(path);
+ }
+ else
+ {
+ DeleteHlsPartialStreamFiles(path);
+ }
+ }
+ catch (IOException ex)
+ {
+ _logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path);
+
+ await DeletePartialStreamFiles(path, jobType, retryCount + 1, 500).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path);
+ }
+ }
+
+ /// <summary>
+ /// Deletes the progressive partial stream files.
+ /// </summary>
+ /// <param name="outputFilePath">The output file path.</param>
+ private void DeleteProgressivePartialStreamFiles(string outputFilePath)
+ {
+ if (File.Exists(outputFilePath))
+ {
+ _fileSystem.DeleteFile(outputFilePath);
+ }
+ }
+
+ /// <summary>
+ /// Deletes the HLS partial stream files.
+ /// </summary>
+ /// <param name="outputFilePath">The output file path.</param>
+ private void DeleteHlsPartialStreamFiles(string outputFilePath)
+ {
+ var directory = Path.GetDirectoryName(outputFilePath);
+ var name = Path.GetFileNameWithoutExtension(outputFilePath);
+
+ var filesToDelete = _fileSystem.GetFilePaths(directory)
+ .Where(f => f.IndexOf(name, StringComparison.OrdinalIgnoreCase) != -1);
+
+ List<Exception>? exs = null;
+ foreach (var file in filesToDelete)
+ {
+ try
+ {
+ _logger.LogDebug("Deleting HLS file {0}", file);
+ _fileSystem.DeleteFile(file);
+ }
+ catch (IOException ex)
+ {
+ (exs ??= new List<Exception>(4)).Add(ex);
+ _logger.LogError(ex, "Error deleting HLS file {Path}", file);
+ }
+ }
+
+ if (exs != null)
+ {
+ throw new AggregateException("Error deleting HLS files", exs);
+ }
+ }
+ }
+}
diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj
index 57af29a92..572cb1af2 100644
--- a/Jellyfin.Api/Jellyfin.Api.csproj
+++ b/Jellyfin.Api/Jellyfin.Api.csproj
@@ -14,9 +14,9 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" />
- <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.5" />
+ <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.6" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
- <PackageReference Include="Swashbuckle.AspNetCore" Version="5.3.3" />
+ <PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="5.3.3" />
</ItemGroup>
diff --git a/Jellyfin.Api/Models/EnvironmentDtos/DefaultDirectoryBrowserInfoDto.cs b/Jellyfin.Api/Models/EnvironmentDtos/DefaultDirectoryBrowserInfoDto.cs
new file mode 100644
index 000000000..92be15b8a
--- /dev/null
+++ b/Jellyfin.Api/Models/EnvironmentDtos/DefaultDirectoryBrowserInfoDto.cs
@@ -0,0 +1,13 @@
+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 000000000..418c11c2d
--- /dev/null
+++ b/Jellyfin.Api/Models/EnvironmentDtos/ValidatePathDto.cs
@@ -0,0 +1,23 @@
+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/LiveTvDtos/ChannelMappingOptionsDto.cs b/Jellyfin.Api/Models/LiveTvDtos/ChannelMappingOptionsDto.cs
new file mode 100644
index 000000000..970d8acdb
--- /dev/null
+++ b/Jellyfin.Api/Models/LiveTvDtos/ChannelMappingOptionsDto.cs
@@ -0,0 +1,36 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.Dto;
+
+namespace Jellyfin.Api.Models.LiveTvDtos
+{
+ /// <summary>
+ /// Channel mapping options dto.
+ /// </summary>
+ public class ChannelMappingOptionsDto
+ {
+ /// <summary>
+ /// Gets or sets list of tuner channels.
+ /// </summary>
+ [SuppressMessage("Microsoft.Performance", "CA2227:ReadOnlyRemoveSetter", MessageId = "TunerChannels", Justification = "Imported from ServiceStack")]
+ public List<TunerChannelMapping> TunerChannels { get; set; } = null!;
+
+ /// <summary>
+ /// Gets or sets list of provider channels.
+ /// </summary>
+ [SuppressMessage("Microsoft.Performance", "CA2227:ReadOnlyRemoveSetter", MessageId = "ProviderChannels", Justification = "Imported from ServiceStack")]
+ public List<NameIdPair> ProviderChannels { get; set; } = null!;
+
+ /// <summary>
+ /// Gets or sets list of mappings.
+ /// </summary>
+ [SuppressMessage("Microsoft.Performance", "CA1819:DontReturnArrays", MessageId = "Mappings", Justification = "Imported from ServiceStack")]
+ public NameValuePair[] Mappings { get; set; } = null!;
+
+ /// <summary>
+ /// Gets or sets provider name.
+ /// </summary>
+ public string? ProviderName { get; set; }
+ }
+}
diff --git a/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs b/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs
new file mode 100644
index 000000000..d7eaab30d
--- /dev/null
+++ b/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs
@@ -0,0 +1,166 @@
+using System;
+
+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>
+ public string? 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.
+ /// Optional.
+ /// </summary>
+ public DateTime? MinStartDate { get; set; }
+
+ /// <summary>
+ /// Gets or sets filter by programs that have completed airing, or not.
+ /// Optional.
+ /// </summary>
+ public bool? HasAired { get; set; }
+
+ /// <summary>
+ /// Gets or sets filter by programs that are currently airing, or not.
+ /// Optional.
+ /// </summary>
+ public bool? IsAiring { get; set; }
+
+ /// <summary>
+ /// Gets or sets the maximum premiere start date.
+ /// Optional.
+ /// </summary>
+ public DateTime? MaxStartDate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the minimum premiere end date.
+ /// Optional.
+ /// </summary>
+ public DateTime? MinEndDate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the maximum premiere end date.
+ /// Optional.
+ /// </summary>
+ public DateTime? MaxEndDate { get; set; }
+
+ /// <summary>
+ /// Gets or sets filter for movies.
+ /// Optional.
+ /// </summary>
+ public bool? IsMovie { get; set; }
+
+ /// <summary>
+ /// Gets or sets filter for series.
+ /// Optional.
+ /// </summary>
+ public bool? IsSeries { get; set; }
+
+ /// <summary>
+ /// Gets or sets filter for news.
+ /// Optional.
+ /// </summary>
+ public bool? IsNews { get; set; }
+
+ /// <summary>
+ /// Gets or sets filter for kids.
+ /// Optional.
+ /// </summary>
+ public bool? IsKids { get; set; }
+
+ /// <summary>
+ /// Gets or sets filter for sports.
+ /// Optional.
+ /// </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.
+ /// Optional.
+ /// </summary>
+ public int? StartIndex { get; set; }
+
+ /// <summary>
+ /// Gets or sets the maximum number of records to return.
+ /// Optional.
+ /// </summary>
+ public int? Limit { get; set; }
+
+ /// <summary>
+ /// Gets or sets specify one or more sort orders, comma delimited. Options: Name, StartDate.
+ /// Optional.
+ /// </summary>
+ public string? SortBy { get; set; }
+
+ /// <summary>
+ /// Gets or sets sort Order - Ascending,Descending.
+ /// </summary>
+ public string? SortOrder { get; set; }
+
+ /// <summary>
+ /// Gets or sets the genres to return guide information for.
+ /// </summary>
+ public string? Genres { get; set; }
+
+ /// <summary>
+ /// Gets or sets the genre ids to return guide information for.
+ /// </summary>
+ public string? GenreIds { get; set; }
+
+ /// <summary>
+ /// Gets or sets include image information in output.
+ /// Optional.
+ /// </summary>
+ public bool? EnableImages { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether retrieve total record count.
+ /// </summary>
+ public bool EnableTotalRecordCount { get; set; } = true;
+
+ /// <summary>
+ /// Gets or sets the max number of images to return, per image type.
+ /// Optional.
+ /// </summary>
+ public int? ImageTypeLimit { get; set; }
+
+ /// <summary>
+ /// Gets or sets the image types to include in the output.
+ /// Optional.
+ /// </summary>
+ public string? EnableImageTypes { get; set; }
+
+ /// <summary>
+ /// Gets or sets include user data.
+ /// Optional.
+ /// </summary>
+ public bool? EnableUserData { get; set; }
+
+ /// <summary>
+ /// Gets or sets filter by series timer id.
+ /// Optional.
+ /// </summary>
+ public string? SeriesTimerId { get; set; }
+
+ /// <summary>
+ /// Gets or sets filter by library series id.
+ /// Optional.
+ /// </summary>
+ public Guid LibrarySeriesId { get; set; }
+
+ /// <summary>
+ /// Gets or sets 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.
+ /// Optional.
+ /// </summary>
+ public string? Fields { get; set; }
+ }
+}
diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs b/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs
new file mode 100644
index 000000000..b9507a4e5
--- /dev/null
+++ b/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs
@@ -0,0 +1,253 @@
+using System;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Dto;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Api.Models.PlaybackDtos
+{
+ /// <summary>
+ /// Class TranscodingJob.
+ /// </summary>
+ public class TranscodingJobDto
+ {
+ /// <summary>
+ /// The process lock.
+ /// </summary>
+ [SuppressMessage("Microsoft.Performance", "CA1051:NoVisibleInstanceFields", MessageId = "ProcessLock", Justification = "Imported from ServiceStack")]
+ [SuppressMessage("Microsoft.Performance", "SA1401:PrivateField", MessageId = "ProcessLock", Justification = "Imported from ServiceStack")]
+ public readonly object ProcessLock = new object();
+
+ /// <summary>
+ /// Timer lock.
+ /// </summary>
+ private readonly object _timerLock = new object();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TranscodingJobDto"/> class.
+ /// </summary>
+ /// <param name="logger">Instance of the <see cref="ILogger{TranscodingJobDto}"/> interface.</param>
+ public TranscodingJobDto(ILogger<TranscodingJobDto> logger)
+ {
+ Logger = logger;
+ }
+
+ /// <summary>
+ /// Gets or sets the play session identifier.
+ /// </summary>
+ /// <value>The play session identifier.</value>
+ public string? PlaySessionId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the live stream identifier.
+ /// </summary>
+ /// <value>The live stream identifier.</value>
+ public string? LiveStreamId { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether is live output.
+ /// </summary>
+ public bool IsLiveOutput { get; set; }
+
+ /// <summary>
+ /// Gets or sets the path.
+ /// </summary>
+ /// <value>The path.</value>
+ public MediaSourceInfo? MediaSource { get; set; }
+
+ /// <summary>
+ /// Gets or sets path.
+ /// </summary>
+ public string? Path { get; set; }
+
+ /// <summary>
+ /// Gets or sets the type.
+ /// </summary>
+ /// <value>The type.</value>
+ public TranscodingJobType Type { get; set; }
+
+ /// <summary>
+ /// Gets or sets the process.
+ /// </summary>
+ /// <value>The process.</value>
+ public Process? Process { get; set; }
+
+ /// <summary>
+ /// Gets logger.
+ /// </summary>
+ public ILogger<TranscodingJobDto> Logger { get; private set; }
+
+ /// <summary>
+ /// Gets or sets the active request count.
+ /// </summary>
+ /// <value>The active request count.</value>
+ public int ActiveRequestCount { get; set; }
+
+ /// <summary>
+ /// Gets or sets the kill timer.
+ /// </summary>
+ /// <value>The kill timer.</value>
+ private Timer? KillTimer { get; set; }
+
+ /// <summary>
+ /// Gets or sets device id.
+ /// </summary>
+ public string? DeviceId { get; set; }
+
+ /// <summary>
+ /// Gets or sets cancellation token source.
+ /// </summary>
+ public CancellationTokenSource? CancellationTokenSource { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether has exited.
+ /// </summary>
+ public bool HasExited { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether is user paused.
+ /// </summary>
+ public bool IsUserPaused { get; set; }
+
+ /// <summary>
+ /// Gets or sets id.
+ /// </summary>
+ public string? Id { get; set; }
+
+ /// <summary>
+ /// Gets or sets framerate.
+ /// </summary>
+ public float? Framerate { get; set; }
+
+ /// <summary>
+ /// Gets or sets completion percentage.
+ /// </summary>
+ public double? CompletionPercentage { get; set; }
+
+ /// <summary>
+ /// Gets or sets bytes downloaded.
+ /// </summary>
+ public long? BytesDownloaded { get; set; }
+
+ /// <summary>
+ /// Gets or sets bytes transcoded.
+ /// </summary>
+ public long? BytesTranscoded { get; set; }
+
+ /// <summary>
+ /// Gets or sets bit rate.
+ /// </summary>
+ public int? BitRate { get; set; }
+
+ /// <summary>
+ /// Gets or sets transcoding position ticks.
+ /// </summary>
+ public long? TranscodingPositionTicks { get; set; }
+
+ /// <summary>
+ /// Gets or sets download position ticks.
+ /// </summary>
+ public long? DownloadPositionTicks { get; set; }
+
+ /// <summary>
+ /// Gets or sets transcoding throttler.
+ /// </summary>
+ public TranscodingThrottler? TranscodingThrottler { get; set; }
+
+ /// <summary>
+ /// Gets or sets last ping date.
+ /// </summary>
+ public DateTime LastPingDate { get; set; }
+
+ /// <summary>
+ /// Gets or sets ping timeout.
+ /// </summary>
+ public int PingTimeout { get; set; }
+
+ /// <summary>
+ /// Stop kill timer.
+ /// </summary>
+ public void StopKillTimer()
+ {
+ lock (_timerLock)
+ {
+ KillTimer?.Change(Timeout.Infinite, Timeout.Infinite);
+ }
+ }
+
+ /// <summary>
+ /// Dispose kill timer.
+ /// </summary>
+ public void DisposeKillTimer()
+ {
+ lock (_timerLock)
+ {
+ if (KillTimer != null)
+ {
+ KillTimer.Dispose();
+ KillTimer = null;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Start kill timer.
+ /// </summary>
+ /// <param name="callback">Callback action.</param>
+ public void StartKillTimer(Action<object> callback)
+ {
+ StartKillTimer(callback, PingTimeout);
+ }
+
+ /// <summary>
+ /// Start kill timer.
+ /// </summary>
+ /// <param name="callback">Callback action.</param>
+ /// <param name="intervalMs">Callback interval.</param>
+ public void StartKillTimer(Action<object> callback, int intervalMs)
+ {
+ if (HasExited)
+ {
+ return;
+ }
+
+ lock (_timerLock)
+ {
+ if (KillTimer == null)
+ {
+ Logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
+ KillTimer = new Timer(new TimerCallback(callback), this, intervalMs, Timeout.Infinite);
+ }
+ else
+ {
+ Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
+ KillTimer.Change(intervalMs, Timeout.Infinite);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Change kill timer if started.
+ /// </summary>
+ public void ChangeKillTimerIfStarted()
+ {
+ if (HasExited)
+ {
+ return;
+ }
+
+ lock (_timerLock)
+ {
+ if (KillTimer != null)
+ {
+ var intervalMs = PingTimeout;
+
+ Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
+ KillTimer.Change(intervalMs, Timeout.Infinite);
+ }
+ }
+ }
+ }
+}
diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
new file mode 100644
index 000000000..b5e42ea29
--- /dev/null
+++ b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
@@ -0,0 +1,212 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.IO;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Api.Models.PlaybackDtos
+{
+ /// <summary>
+ /// Transcoding throttler.
+ /// </summary>
+ public class TranscodingThrottler : IDisposable
+ {
+ private readonly TranscodingJobDto _job;
+ private readonly ILogger<TranscodingThrottler> _logger;
+ private readonly IConfigurationManager _config;
+ private readonly IFileSystem _fileSystem;
+ private Timer? _timer;
+ private bool _isPaused;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TranscodingThrottler"/> class.
+ /// </summary>
+ /// <param name="job">Transcoding job dto.</param>
+ /// <param name="logger">Instance of the <see cref="ILogger{TranscodingThrottler}"/> interface.</param>
+ /// <param name="config">Instance of the <see cref="IConfigurationManager"/> interface.</param>
+ /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+ public TranscodingThrottler(TranscodingJobDto job, ILogger<TranscodingThrottler> logger, IConfigurationManager config, IFileSystem fileSystem)
+ {
+ _job = job;
+ _logger = logger;
+ _config = config;
+ _fileSystem = fileSystem;
+ }
+
+ /// <summary>
+ /// Start timer.
+ /// </summary>
+ public void Start()
+ {
+ _timer = new Timer(TimerCallback, null, 5000, 5000);
+ }
+
+ /// <summary>
+ /// Unpause transcoding.
+ /// </summary>
+ /// <returns>A <see cref="Task"/>.</returns>
+ public async Task UnpauseTranscoding()
+ {
+ if (_isPaused)
+ {
+ _logger.LogDebug("Sending resume command to ffmpeg");
+
+ try
+ {
+ await _job.Process!.StandardInput.WriteLineAsync().ConfigureAwait(false);
+ _isPaused = false;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error resuming transcoding");
+ }
+ }
+ }
+
+ /// <summary>
+ /// Stop throttler.
+ /// </summary>
+ /// <returns>A <see cref="Task"/>.</returns>
+ public async Task Stop()
+ {
+ DisposeTimer();
+ await UnpauseTranscoding().ConfigureAwait(false);
+ }
+
+ /// <summary>
+ /// Dispose throttler.
+ /// </summary>
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// Dispose throttler.
+ /// </summary>
+ /// <param name="disposing">Disposing.</param>
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ DisposeTimer();
+ }
+ }
+
+ private EncodingOptions GetOptions()
+ {
+ return _config.GetConfiguration<EncodingOptions>("encoding");
+ }
+
+ private async void TimerCallback(object state)
+ {
+ if (_job.HasExited)
+ {
+ DisposeTimer();
+ return;
+ }
+
+ var options = GetOptions();
+
+ if (options.EnableThrottling && IsThrottleAllowed(_job, options.ThrottleDelaySeconds))
+ {
+ await PauseTranscoding().ConfigureAwait(false);
+ }
+ else
+ {
+ await UnpauseTranscoding().ConfigureAwait(false);
+ }
+ }
+
+ private async Task PauseTranscoding()
+ {
+ if (!_isPaused)
+ {
+ _logger.LogDebug("Sending pause command to ffmpeg");
+
+ try
+ {
+ await _job.Process!.StandardInput.WriteAsync("c").ConfigureAwait(false);
+ _isPaused = true;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error pausing transcoding");
+ }
+ }
+ }
+
+ private bool IsThrottleAllowed(TranscodingJobDto job, int thresholdSeconds)
+ {
+ var bytesDownloaded = job.BytesDownloaded ?? 0;
+ var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
+ var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
+
+ var path = job.Path;
+ var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks;
+
+ if (downloadPositionTicks > 0 && transcodingPositionTicks > 0)
+ {
+ // HLS - time-based consideration
+
+ var targetGap = gapLengthInTicks;
+ var gap = transcodingPositionTicks - downloadPositionTicks;
+
+ if (gap < targetGap)
+ {
+ _logger.LogDebug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap);
+ return false;
+ }
+
+ _logger.LogDebug("Throttling transcoder gap {0} target gap {1}", gap, targetGap);
+ return true;
+ }
+
+ if (bytesDownloaded > 0 && transcodingPositionTicks > 0)
+ {
+ // Progressive Streaming - byte-based consideration
+
+ try
+ {
+ var bytesTranscoded = job.BytesTranscoded ?? _fileSystem.GetFileInfo(path).Length;
+
+ // Estimate the bytes the transcoder should be ahead
+ double gapFactor = gapLengthInTicks;
+ gapFactor /= transcodingPositionTicks;
+ var targetGap = bytesTranscoded * gapFactor;
+
+ var gap = bytesTranscoded - bytesDownloaded;
+
+ if (gap < targetGap)
+ {
+ _logger.LogDebug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
+ return false;
+ }
+
+ _logger.LogDebug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error getting output size");
+ return false;
+ }
+ }
+
+ _logger.LogDebug("No throttle data for " + path);
+ return false;
+ }
+
+ private void DisposeTimer()
+ {
+ if (_timer != null)
+ {
+ _timer.Dispose();
+ _timer = null;
+ }
+ }
+ }
+}
diff --git a/Jellyfin.Api/Models/UserViewDtos/SpecialViewOptionDto.cs b/Jellyfin.Api/Models/UserViewDtos/SpecialViewOptionDto.cs
new file mode 100644
index 000000000..84b6b0958
--- /dev/null
+++ b/Jellyfin.Api/Models/UserViewDtos/SpecialViewOptionDto.cs
@@ -0,0 +1,18 @@
+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.Data/Entities/Artwork.cs b/Jellyfin.Data/Entities/Artwork.cs
index bf3029368..6ed32eac3 100644
--- a/Jellyfin.Data/Entities/Artwork.cs
+++ b/Jellyfin.Data/Entities/Artwork.cs
@@ -24,7 +24,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="path"></param>
/// <param name="kind"></param>
@@ -32,17 +32,28 @@ namespace Jellyfin.Data.Entities
/// <param name="_personrole1"></param>
public Artwork(string path, Enums.ArtKind kind, Metadata _metadata0, PersonRole _personrole1)
{
- if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path));
+ if (string.IsNullOrEmpty(path))
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
this.Path = path;
this.Kind = kind;
- if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0));
+ if (_metadata0 == null)
+ {
+ throw new ArgumentNullException(nameof(_metadata0));
+ }
+
_metadata0.Artwork.Add(this);
- if (_personrole1 == null) throw new ArgumentNullException(nameof(_personrole1));
- _personrole1.Artwork = this;
+ if (_personrole1 == null)
+ {
+ throw new ArgumentNullException(nameof(_personrole1));
+ }
+ _personrole1.Artwork = this;
Init();
}
@@ -64,7 +75,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Id
+ /// Backing field for Id.
/// </summary>
internal int _Id;
/// <summary>
@@ -77,7 +88,7 @@ namespace Jellyfin.Data.Entities
partial void GetId(ref int result);
/// <summary>
- /// Identity, Indexed, Required
+ /// Identity, Indexed, Required.
/// </summary>
[Key]
[Required]
@@ -87,8 +98,9 @@ namespace Jellyfin.Data.Entities
{
int value = _Id;
GetId(ref value);
- return (_Id = value);
+ return _Id = value;
}
+
protected set
{
int oldValue = _Id;
@@ -101,7 +113,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Path
+ /// Backing field for Path.
/// </summary>
protected string _Path;
/// <summary>
@@ -125,8 +137,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Path;
GetPath(ref value);
- return (_Path = value);
+ return _Path = value;
}
+
set
{
string oldValue = _Path;
@@ -139,7 +152,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Kind
+ /// Backing field for Kind.
/// </summary>
internal Enums.ArtKind _Kind;
/// <summary>
@@ -152,7 +165,7 @@ namespace Jellyfin.Data.Entities
partial void GetKind(ref Enums.ArtKind result);
/// <summary>
- /// Indexed, Required
+ /// Indexed, Required.
/// </summary>
[Required]
public Enums.ArtKind Kind
@@ -161,8 +174,9 @@ namespace Jellyfin.Data.Entities
{
Enums.ArtKind value = _Kind;
GetKind(ref value);
- return (_Kind = value);
+ return _Kind = value;
}
+
set
{
Enums.ArtKind oldValue = _Kind;
@@ -175,7 +189,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrenyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -189,7 +203,6 @@ namespace Jellyfin.Data.Entities
/*************************************************************************
* Navigation properties
*************************************************************************/
-
}
}
diff --git a/Jellyfin.Data/Entities/Book.cs b/Jellyfin.Data/Entities/Book.cs
index 42d24e31d..c4d12496e 100644
--- a/Jellyfin.Data/Entities/Book.cs
+++ b/Jellyfin.Data/Entities/Book.cs
@@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
public Book(Guid urlid, DateTime dateadded)
@@ -63,7 +63,6 @@ namespace Jellyfin.Data.Entities
[ForeignKey("Release_Releases_Id")]
public virtual ICollection<Release> Releases { get; protected set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/BookMetadata.cs b/Jellyfin.Data/Entities/BookMetadata.cs
index d52fe7605..df43090d3 100644
--- a/Jellyfin.Data/Entities/BookMetadata.cs
+++ b/Jellyfin.Data/Entities/BookMetadata.cs
@@ -27,20 +27,32 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
- /// <param name="title">The title or name of the object</param>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="title">The title or name of the object.</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="_book0"></param>
public BookMetadata(string title, string language, DateTime dateadded, DateTime datemodified, Book _book0)
{
- if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ if (string.IsNullOrEmpty(title))
+ {
+ throw new ArgumentNullException(nameof(title));
+ }
+
this.Title = title;
- if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ if (string.IsNullOrEmpty(language))
+ {
+ throw new ArgumentNullException(nameof(language));
+ }
+
this.Language = language;
- if (_book0 == null) throw new ArgumentNullException(nameof(_book0));
+ if (_book0 == null)
+ {
+ throw new ArgumentNullException(nameof(_book0));
+ }
+
_book0.BookMetadata.Add(this);
this.Publishers = new HashSet<Company>();
@@ -51,8 +63,8 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Static create function (for use in LINQ queries, etc.)
/// </summary>
- /// <param name="title">The title or name of the object</param>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="title">The title or name of the object.</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="_book0"></param>
public static BookMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, Book _book0)
{
@@ -64,7 +76,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for ISBN
+ /// Backing field for ISBN.
/// </summary>
protected long? _ISBN;
/// <summary>
@@ -82,8 +94,9 @@ namespace Jellyfin.Data.Entities
{
long? value = _ISBN;
GetISBN(ref value);
- return (_ISBN = value);
+ return _ISBN = value;
}
+
set
{
long? oldValue = _ISBN;
@@ -101,7 +114,6 @@ namespace Jellyfin.Data.Entities
[ForeignKey("Company_Publishers_Id")]
public virtual ICollection<Company> Publishers { get; protected set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/Chapter.cs b/Jellyfin.Data/Entities/Chapter.cs
index d48cb9b62..4575cdb4d 100644
--- a/Jellyfin.Data/Entities/Chapter.cs
+++ b/Jellyfin.Data/Entities/Chapter.cs
@@ -25,19 +25,27 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="timestart"></param>
/// <param name="_release0"></param>
public Chapter(string language, long timestart, Release _release0)
{
- if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ if (string.IsNullOrEmpty(language))
+ {
+ throw new ArgumentNullException(nameof(language));
+ }
+
this.Language = language;
this.TimeStart = timestart;
- if (_release0 == null) throw new ArgumentNullException(nameof(_release0));
+ if (_release0 == null)
+ {
+ throw new ArgumentNullException(nameof(_release0));
+ }
+
_release0.Chapters.Add(this);
@@ -47,7 +55,7 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Static create function (for use in LINQ queries, etc.)
/// </summary>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="timestart"></param>
/// <param name="_release0"></param>
public static Chapter Create(string language, long timestart, Release _release0)
@@ -60,7 +68,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Id
+ /// Backing field for Id.
/// </summary>
internal int _Id;
/// <summary>
@@ -73,7 +81,7 @@ namespace Jellyfin.Data.Entities
partial void GetId(ref int result);
/// <summary>
- /// Identity, Indexed, Required
+ /// Identity, Indexed, Required.
/// </summary>
[Key]
[Required]
@@ -84,8 +92,9 @@ namespace Jellyfin.Data.Entities
{
int value = _Id;
GetId(ref value);
- return (_Id = value);
+ return _Id = value;
}
+
protected set
{
int oldValue = _Id;
@@ -98,7 +107,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Name
+ /// Backing field for Name.
/// </summary>
protected string _Name;
/// <summary>
@@ -121,8 +130,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Name;
GetName(ref value);
- return (_Name = value);
+ return _Name = value;
}
+
set
{
string oldValue = _Name;
@@ -135,7 +145,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Language
+ /// Backing field for Language.
/// </summary>
protected string _Language;
/// <summary>
@@ -149,7 +159,7 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Required, Min length = 3, Max length = 3
- /// ISO-639-3 3-character language codes
+ /// ISO-639-3 3-character language codes.
/// </summary>
[Required]
[MinLength(3)]
@@ -161,8 +171,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Language;
GetLanguage(ref value);
- return (_Language = value);
+ return _Language = value;
}
+
set
{
string oldValue = _Language;
@@ -175,7 +186,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for TimeStart
+ /// Backing field for TimeStart.
/// </summary>
protected long _TimeStart;
/// <summary>
@@ -188,7 +199,7 @@ namespace Jellyfin.Data.Entities
partial void GetTimeStart(ref long result);
/// <summary>
- /// Required
+ /// Required.
/// </summary>
[Required]
public long TimeStart
@@ -197,8 +208,9 @@ namespace Jellyfin.Data.Entities
{
long value = _TimeStart;
GetTimeStart(ref value);
- return (_TimeStart = value);
+ return _TimeStart = value;
}
+
set
{
long oldValue = _TimeStart;
@@ -211,7 +223,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for TimeEnd
+ /// Backing field for TimeEnd.
/// </summary>
protected long? _TimeEnd;
/// <summary>
@@ -229,8 +241,9 @@ namespace Jellyfin.Data.Entities
{
long? value = _TimeEnd;
GetTimeEnd(ref value);
- return (_TimeEnd = value);
+ return _TimeEnd = value;
}
+
set
{
long? oldValue = _TimeEnd;
@@ -243,7 +256,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrenyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -257,7 +270,6 @@ namespace Jellyfin.Data.Entities
/*************************************************************************
* Navigation properties
*************************************************************************/
-
}
}
diff --git a/Jellyfin.Data/Entities/Collection.cs b/Jellyfin.Data/Entities/Collection.cs
index e2fa3a5bd..01836d893 100644
--- a/Jellyfin.Data/Entities/Collection.cs
+++ b/Jellyfin.Data/Entities/Collection.cs
@@ -9,7 +9,7 @@ namespace Jellyfin.Data.Entities
partial void Init();
/// <summary>
- /// Default constructor
+ /// Default constructor.
/// </summary>
public Collection()
{
@@ -23,7 +23,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Id
+ /// Backing field for Id.
/// </summary>
internal int _Id;
/// <summary>
@@ -36,7 +36,7 @@ namespace Jellyfin.Data.Entities
partial void GetId(ref int result);
/// <summary>
- /// Identity, Indexed, Required
+ /// Identity, Indexed, Required.
/// </summary>
[Key]
[Required]
@@ -47,8 +47,9 @@ namespace Jellyfin.Data.Entities
{
int value = _Id;
GetId(ref value);
- return (_Id = value);
+ return _Id = value;
}
+
protected set
{
int oldValue = _Id;
@@ -61,7 +62,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Name
+ /// Backing field for Name.
/// </summary>
protected string _Name;
/// <summary>
@@ -84,8 +85,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Name;
GetName(ref value);
- return (_Name = value);
+ return _Name = value;
}
+
set
{
string oldValue = _Name;
@@ -98,7 +100,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrenyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -114,7 +116,6 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
[ForeignKey("CollectionItem_CollectionItem_Id")]
public virtual ICollection<CollectionItem> CollectionItem { get; protected set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/CollectionItem.cs b/Jellyfin.Data/Entities/CollectionItem.cs
index 4a3d06639..d879806ee 100644
--- a/Jellyfin.Data/Entities/CollectionItem.cs
+++ b/Jellyfin.Data/Entities/CollectionItem.cs
@@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="_collection0"></param>
/// <param name="_collectionitem1"></param>
@@ -38,15 +38,26 @@ namespace Jellyfin.Data.Entities
// NOTE: This class has one-to-one associations with CollectionItem.
// One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other.
- if (_collection0 == null) throw new ArgumentNullException(nameof(_collection0));
+ if (_collection0 == null)
+ {
+ throw new ArgumentNullException(nameof(_collection0));
+ }
+
_collection0.CollectionItem.Add(this);
- if (_collectionitem1 == null) throw new ArgumentNullException(nameof(_collectionitem1));
+ if (_collectionitem1 == null)
+ {
+ throw new ArgumentNullException(nameof(_collectionitem1));
+ }
+
_collectionitem1.Next = this;
- if (_collectionitem2 == null) throw new ArgumentNullException(nameof(_collectionitem2));
- _collectionitem2.Previous = this;
+ if (_collectionitem2 == null)
+ {
+ throw new ArgumentNullException(nameof(_collectionitem2));
+ }
+ _collectionitem2.Previous = this;
Init();
}
@@ -67,7 +78,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Id
+ /// Backing field for Id.
/// </summary>
internal int _Id;
/// <summary>
@@ -80,7 +91,7 @@ namespace Jellyfin.Data.Entities
partial void GetId(ref int result);
/// <summary>
- /// Identity, Indexed, Required
+ /// Identity, Indexed, Required.
/// </summary>
[Key]
[Required]
@@ -91,8 +102,9 @@ namespace Jellyfin.Data.Entities
{
int value = _Id;
GetId(ref value);
- return (_Id = value);
+ return _Id = value;
}
+
protected set
{
int oldValue = _Id;
@@ -105,7 +117,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrenyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -121,7 +133,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Required
+ /// Required.
/// </summary>
[ForeignKey("LibraryItem_Id")]
public virtual LibraryItem LibraryItem { get; set; }
@@ -137,7 +149,6 @@ namespace Jellyfin.Data.Entities
/// </remarks>
[ForeignKey("CollectionItem_Previous_Id")]
public virtual CollectionItem Previous { get; set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/Company.cs b/Jellyfin.Data/Entities/Company.cs
index 0650271c6..e905a17da 100644
--- a/Jellyfin.Data/Entities/Company.cs
+++ b/Jellyfin.Data/Entities/Company.cs
@@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="_moviemetadata0"></param>
/// <param name="_seriesmetadata1"></param>
@@ -37,19 +37,39 @@ namespace Jellyfin.Data.Entities
/// <param name="_company4"></param>
public Company(MovieMetadata _moviemetadata0, SeriesMetadata _seriesmetadata1, MusicAlbumMetadata _musicalbummetadata2, BookMetadata _bookmetadata3, Company _company4)
{
- if (_moviemetadata0 == null) throw new ArgumentNullException(nameof(_moviemetadata0));
+ if (_moviemetadata0 == null)
+ {
+ throw new ArgumentNullException(nameof(_moviemetadata0));
+ }
+
_moviemetadata0.Studios.Add(this);
- if (_seriesmetadata1 == null) throw new ArgumentNullException(nameof(_seriesmetadata1));
+ if (_seriesmetadata1 == null)
+ {
+ throw new ArgumentNullException(nameof(_seriesmetadata1));
+ }
+
_seriesmetadata1.Networks.Add(this);
- if (_musicalbummetadata2 == null) throw new ArgumentNullException(nameof(_musicalbummetadata2));
+ if (_musicalbummetadata2 == null)
+ {
+ throw new ArgumentNullException(nameof(_musicalbummetadata2));
+ }
+
_musicalbummetadata2.Labels.Add(this);
- if (_bookmetadata3 == null) throw new ArgumentNullException(nameof(_bookmetadata3));
+ if (_bookmetadata3 == null)
+ {
+ throw new ArgumentNullException(nameof(_bookmetadata3));
+ }
+
_bookmetadata3.Publishers.Add(this);
- if (_company4 == null) throw new ArgumentNullException(nameof(_company4));
+ if (_company4 == null)
+ {
+ throw new ArgumentNullException(nameof(_company4));
+ }
+
_company4.Parent = this;
this.CompanyMetadata = new HashSet<CompanyMetadata>();
@@ -75,7 +95,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Id
+ /// Backing field for Id.
/// </summary>
internal int _Id;
/// <summary>
@@ -88,7 +108,7 @@ namespace Jellyfin.Data.Entities
partial void GetId(ref int result);
/// <summary>
- /// Identity, Indexed, Required
+ /// Identity, Indexed, Required.
/// </summary>
[Key]
[Required]
@@ -99,8 +119,9 @@ namespace Jellyfin.Data.Entities
{
int value = _Id;
GetId(ref value);
- return (_Id = value);
+ return _Id = value;
}
+
protected set
{
int oldValue = _Id;
@@ -113,7 +134,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrenyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -131,7 +152,6 @@ namespace Jellyfin.Data.Entities
public virtual ICollection<CompanyMetadata> CompanyMetadata { get; protected set; }
[ForeignKey("Company_Parent_Id")]
public virtual Company Parent { get; set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/CompanyMetadata.cs b/Jellyfin.Data/Entities/CompanyMetadata.cs
index b3ec9c1a7..e75349cf2 100644
--- a/Jellyfin.Data/Entities/CompanyMetadata.cs
+++ b/Jellyfin.Data/Entities/CompanyMetadata.cs
@@ -24,22 +24,33 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
- /// <param name="title">The title or name of the object</param>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="title">The title or name of the object.</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="_company0"></param>
public CompanyMetadata(string title, string language, DateTime dateadded, DateTime datemodified, Company _company0)
{
- if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ if (string.IsNullOrEmpty(title))
+ {
+ throw new ArgumentNullException(nameof(title));
+ }
+
this.Title = title;
- if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ if (string.IsNullOrEmpty(language))
+ {
+ throw new ArgumentNullException(nameof(language));
+ }
+
this.Language = language;
- if (_company0 == null) throw new ArgumentNullException(nameof(_company0));
- _company0.CompanyMetadata.Add(this);
+ if (_company0 == null)
+ {
+ throw new ArgumentNullException(nameof(_company0));
+ }
+ _company0.CompanyMetadata.Add(this);
Init();
}
@@ -47,8 +58,8 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Static create function (for use in LINQ queries, etc.)
/// </summary>
- /// <param name="title">The title or name of the object</param>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="title">The title or name of the object.</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="_company0"></param>
public static CompanyMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, Company _company0)
{
@@ -60,7 +71,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Description
+ /// Backing field for Description.
/// </summary>
protected string _Description;
/// <summary>
@@ -83,8 +94,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Description;
GetDescription(ref value);
- return (_Description = value);
+ return _Description = value;
}
+
set
{
string oldValue = _Description;
@@ -97,7 +109,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Headquarters
+ /// Backing field for Headquarters.
/// </summary>
protected string _Headquarters;
/// <summary>
@@ -120,8 +132,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Headquarters;
GetHeadquarters(ref value);
- return (_Headquarters = value);
+ return _Headquarters = value;
}
+
set
{
string oldValue = _Headquarters;
@@ -134,7 +147,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Country
+ /// Backing field for Country.
/// </summary>
protected string _Country;
/// <summary>
@@ -157,8 +170,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Country;
GetCountry(ref value);
- return (_Country = value);
+ return _Country = value;
}
+
set
{
string oldValue = _Country;
@@ -171,7 +185,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Homepage
+ /// Backing field for Homepage.
/// </summary>
protected string _Homepage;
/// <summary>
@@ -194,8 +208,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Homepage;
GetHomepage(ref value);
- return (_Homepage = value);
+ return _Homepage = value;
}
+
set
{
string oldValue = _Homepage;
@@ -210,7 +225,6 @@ namespace Jellyfin.Data.Entities
/*************************************************************************
* Navigation properties
*************************************************************************/
-
}
}
diff --git a/Jellyfin.Data/Entities/CustomItem.cs b/Jellyfin.Data/Entities/CustomItem.cs
index 2006717bf..446391591 100644
--- a/Jellyfin.Data/Entities/CustomItem.cs
+++ b/Jellyfin.Data/Entities/CustomItem.cs
@@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
public CustomItem(Guid urlid, DateTime dateadded)
@@ -62,7 +62,6 @@ namespace Jellyfin.Data.Entities
[ForeignKey("Release_Releases_Id")]
public virtual ICollection<Release> Releases { get; protected set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/CustomItemMetadata.cs b/Jellyfin.Data/Entities/CustomItemMetadata.cs
index e09e4467a..965ed731f 100644
--- a/Jellyfin.Data/Entities/CustomItemMetadata.cs
+++ b/Jellyfin.Data/Entities/CustomItemMetadata.cs
@@ -23,22 +23,33 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
- /// <param name="title">The title or name of the object</param>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="title">The title or name of the object.</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="_customitem0"></param>
public CustomItemMetadata(string title, string language, DateTime dateadded, DateTime datemodified, CustomItem _customitem0)
{
- if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ if (string.IsNullOrEmpty(title))
+ {
+ throw new ArgumentNullException(nameof(title));
+ }
+
this.Title = title;
- if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ if (string.IsNullOrEmpty(language))
+ {
+ throw new ArgumentNullException(nameof(language));
+ }
+
this.Language = language;
- if (_customitem0 == null) throw new ArgumentNullException(nameof(_customitem0));
- _customitem0.CustomItemMetadata.Add(this);
+ if (_customitem0 == null)
+ {
+ throw new ArgumentNullException(nameof(_customitem0));
+ }
+ _customitem0.CustomItemMetadata.Add(this);
Init();
}
@@ -46,8 +57,8 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Static create function (for use in LINQ queries, etc.)
/// </summary>
- /// <param name="title">The title or name of the object</param>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="title">The title or name of the object.</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="_customitem0"></param>
public static CustomItemMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, CustomItem _customitem0)
{
@@ -61,7 +72,6 @@ namespace Jellyfin.Data.Entities
/*************************************************************************
* Navigation properties
*************************************************************************/
-
}
}
diff --git a/Jellyfin.Data/Entities/Episode.cs b/Jellyfin.Data/Entities/Episode.cs
index 6f6baa14d..57fbf894b 100644
--- a/Jellyfin.Data/Entities/Episode.cs
+++ b/Jellyfin.Data/Entities/Episode.cs
@@ -31,7 +31,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
/// <param name="_season0"></param>
@@ -42,7 +42,11 @@ namespace Jellyfin.Data.Entities
this.UrlId = urlid;
- if (_season0 == null) throw new ArgumentNullException(nameof(_season0));
+ if (_season0 == null)
+ {
+ throw new ArgumentNullException(nameof(_season0));
+ }
+
_season0.Episodes.Add(this);
this.Releases = new HashSet<Release>();
@@ -66,7 +70,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for EpisodeNumber
+ /// Backing field for EpisodeNumber.
/// </summary>
protected int? _EpisodeNumber;
/// <summary>
@@ -84,8 +88,9 @@ namespace Jellyfin.Data.Entities
{
int? value = _EpisodeNumber;
GetEpisodeNumber(ref value);
- return (_EpisodeNumber = value);
+ return _EpisodeNumber = value;
}
+
set
{
int? oldValue = _EpisodeNumber;
@@ -104,7 +109,6 @@ namespace Jellyfin.Data.Entities
public virtual ICollection<Release> Releases { get; protected set; }
[ForeignKey("EpisodeMetadata_EpisodeMetadata_Id")]
public virtual ICollection<EpisodeMetadata> EpisodeMetadata { get; protected set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/EpisodeMetadata.cs b/Jellyfin.Data/Entities/EpisodeMetadata.cs
index e5431bf22..9a21fd50f 100644
--- a/Jellyfin.Data/Entities/EpisodeMetadata.cs
+++ b/Jellyfin.Data/Entities/EpisodeMetadata.cs
@@ -24,22 +24,33 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
- /// <param name="title">The title or name of the object</param>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="title">The title or name of the object.</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="_episode0"></param>
public EpisodeMetadata(string title, string language, DateTime dateadded, DateTime datemodified, Episode _episode0)
{
- if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ if (string.IsNullOrEmpty(title))
+ {
+ throw new ArgumentNullException(nameof(title));
+ }
+
this.Title = title;
- if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ if (string.IsNullOrEmpty(language))
+ {
+ throw new ArgumentNullException(nameof(language));
+ }
+
this.Language = language;
- if (_episode0 == null) throw new ArgumentNullException(nameof(_episode0));
- _episode0.EpisodeMetadata.Add(this);
+ if (_episode0 == null)
+ {
+ throw new ArgumentNullException(nameof(_episode0));
+ }
+ _episode0.EpisodeMetadata.Add(this);
Init();
}
@@ -47,8 +58,8 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Static create function (for use in LINQ queries, etc.)
/// </summary>
- /// <param name="title">The title or name of the object</param>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="title">The title or name of the object.</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="_episode0"></param>
public static EpisodeMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, Episode _episode0)
{
@@ -60,7 +71,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Outline
+ /// Backing field for Outline.
/// </summary>
protected string _Outline;
/// <summary>
@@ -83,8 +94,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Outline;
GetOutline(ref value);
- return (_Outline = value);
+ return _Outline = value;
}
+
set
{
string oldValue = _Outline;
@@ -97,7 +109,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Plot
+ /// Backing field for Plot.
/// </summary>
protected string _Plot;
/// <summary>
@@ -120,8 +132,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Plot;
GetPlot(ref value);
- return (_Plot = value);
+ return _Plot = value;
}
+
set
{
string oldValue = _Plot;
@@ -134,7 +147,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Tagline
+ /// Backing field for Tagline.
/// </summary>
protected string _Tagline;
/// <summary>
@@ -157,8 +170,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Tagline;
GetTagline(ref value);
- return (_Tagline = value);
+ return _Tagline = value;
}
+
set
{
string oldValue = _Tagline;
@@ -173,7 +187,6 @@ namespace Jellyfin.Data.Entities
/*************************************************************************
* Navigation properties
*************************************************************************/
-
}
}
diff --git a/Jellyfin.Data/Entities/Genre.cs b/Jellyfin.Data/Entities/Genre.cs
index 38f289a8e..24e6815d8 100644
--- a/Jellyfin.Data/Entities/Genre.cs
+++ b/Jellyfin.Data/Entities/Genre.cs
@@ -25,18 +25,25 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="name"></param>
/// <param name="_metadata0"></param>
public Genre(string name, Metadata _metadata0)
{
- if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+
this.Name = name;
- if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0));
- _metadata0.Genres.Add(this);
+ if (_metadata0 == null)
+ {
+ throw new ArgumentNullException(nameof(_metadata0));
+ }
+ _metadata0.Genres.Add(this);
Init();
}
@@ -56,7 +63,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Id
+ /// Backing field for Id.
/// </summary>
internal int _Id;
/// <summary>
@@ -69,7 +76,7 @@ namespace Jellyfin.Data.Entities
partial void GetId(ref int result);
/// <summary>
- /// Identity, Indexed, Required
+ /// Identity, Indexed, Required.
/// </summary>
[Key]
[Required]
@@ -80,8 +87,9 @@ namespace Jellyfin.Data.Entities
{
int value = _Id;
GetId(ref value);
- return (_Id = value);
+ return _Id = value;
}
+
protected set
{
int oldValue = _Id;
@@ -94,7 +102,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Name
+ /// Backing field for Name.
/// </summary>
internal string _Name;
/// <summary>
@@ -118,8 +126,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Name;
GetName(ref value);
- return (_Name = value);
+ return _Name = value;
}
+
set
{
string oldValue = _Name;
@@ -132,7 +141,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrenyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -146,7 +155,6 @@ namespace Jellyfin.Data.Entities
/*************************************************************************
* Navigation properties
*************************************************************************/
-
}
}
diff --git a/Jellyfin.Data/Entities/Group.cs b/Jellyfin.Data/Entities/Group.cs
index 5cbb126f9..47833378e 100644
--- a/Jellyfin.Data/Entities/Group.cs
+++ b/Jellyfin.Data/Entities/Group.cs
@@ -99,7 +99,7 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Static create function (for use in LINQ queries, etc.)
/// </summary>
- /// <param name="name">The name of this group</param>
+ /// <param name="name">The name of this group.</param>
public static Group Create(string name)
{
return new Group(name);
diff --git a/Jellyfin.Data/Entities/Library.cs b/Jellyfin.Data/Entities/Library.cs
index c11c09e91..d935e43b1 100644
--- a/Jellyfin.Data/Entities/Library.cs
+++ b/Jellyfin.Data/Entities/Library.cs
@@ -25,14 +25,17 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="name"></param>
public Library(string name)
{
- if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
- this.Name = name;
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+ this.Name = name;
Init();
}
@@ -51,7 +54,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Id
+ /// Backing field for Id.
/// </summary>
internal int _Id;
/// <summary>
@@ -64,7 +67,7 @@ namespace Jellyfin.Data.Entities
partial void GetId(ref int result);
/// <summary>
- /// Identity, Indexed, Required
+ /// Identity, Indexed, Required.
/// </summary>
[Key]
[Required]
@@ -75,8 +78,9 @@ namespace Jellyfin.Data.Entities
{
int value = _Id;
GetId(ref value);
- return (_Id = value);
+ return _Id = value;
}
+
protected set
{
int oldValue = _Id;
@@ -89,7 +93,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Name
+ /// Backing field for Name.
/// </summary>
protected string _Name;
/// <summary>
@@ -113,8 +117,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Name;
GetName(ref value);
- return (_Name = value);
+ return _Name = value;
}
+
set
{
string oldValue = _Name;
@@ -127,7 +132,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrenyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -141,7 +146,6 @@ namespace Jellyfin.Data.Entities
/*************************************************************************
* Navigation properties
*************************************************************************/
-
}
}
diff --git a/Jellyfin.Data/Entities/LibraryItem.cs b/Jellyfin.Data/Entities/LibraryItem.cs
index af6c640b9..f41753560 100644
--- a/Jellyfin.Data/Entities/LibraryItem.cs
+++ b/Jellyfin.Data/Entities/LibraryItem.cs
@@ -17,7 +17,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
protected LibraryItem(Guid urlid, DateTime dateadded)
@@ -33,7 +33,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Id
+ /// Backing field for Id.
/// </summary>
internal int _Id;
/// <summary>
@@ -46,7 +46,7 @@ namespace Jellyfin.Data.Entities
partial void GetId(ref int result);
/// <summary>
- /// Identity, Indexed, Required
+ /// Identity, Indexed, Required.
/// </summary>
[Key]
[Required]
@@ -57,8 +57,9 @@ namespace Jellyfin.Data.Entities
{
int value = _Id;
GetId(ref value);
- return (_Id = value);
+ return _Id = value;
}
+
protected set
{
int oldValue = _Id;
@@ -71,7 +72,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for UrlId
+ /// Backing field for UrlId.
/// </summary>
internal Guid _UrlId;
/// <summary>
@@ -94,8 +95,9 @@ namespace Jellyfin.Data.Entities
{
Guid value = _UrlId;
GetUrlId(ref value);
- return (_UrlId = value);
+ return _UrlId = value;
}
+
set
{
Guid oldValue = _UrlId;
@@ -108,7 +110,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for DateAdded
+ /// Backing field for DateAdded.
/// </summary>
protected DateTime _DateAdded;
/// <summary>
@@ -121,7 +123,7 @@ namespace Jellyfin.Data.Entities
partial void GetDateAdded(ref DateTime result);
/// <summary>
- /// Required
+ /// Required.
/// </summary>
[Required]
public DateTime DateAdded
@@ -130,8 +132,9 @@ namespace Jellyfin.Data.Entities
{
DateTime value = _DateAdded;
GetDateAdded(ref value);
- return (_DateAdded = value);
+ return _DateAdded = value;
}
+
internal set
{
DateTime oldValue = _DateAdded;
@@ -144,7 +147,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrenyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -160,11 +163,10 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Required
+ /// Required.
/// </summary>
[ForeignKey("LibraryRoot_Id")]
public virtual LibraryRoot LibraryRoot { get; set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/LibraryRoot.cs b/Jellyfin.Data/Entities/LibraryRoot.cs
index bbc23e1c9..9695ed638 100644
--- a/Jellyfin.Data/Entities/LibraryRoot.cs
+++ b/Jellyfin.Data/Entities/LibraryRoot.cs
@@ -25,14 +25,17 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
- /// <param name="path">Absolute Path</param>
+ /// <param name="path">Absolute Path.</param>
public LibraryRoot(string path)
{
- if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path));
- this.Path = path;
+ if (string.IsNullOrEmpty(path))
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+ this.Path = path;
Init();
}
@@ -40,7 +43,7 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Static create function (for use in LINQ queries, etc.)
/// </summary>
- /// <param name="path">Absolute Path</param>
+ /// <param name="path">Absolute Path.</param>
public static LibraryRoot Create(string path)
{
return new LibraryRoot(path);
@@ -51,7 +54,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Id
+ /// Backing field for Id.
/// </summary>
internal int _Id;
/// <summary>
@@ -64,7 +67,7 @@ namespace Jellyfin.Data.Entities
partial void GetId(ref int result);
/// <summary>
- /// Identity, Indexed, Required
+ /// Identity, Indexed, Required.
/// </summary>
[Key]
[Required]
@@ -75,8 +78,9 @@ namespace Jellyfin.Data.Entities
{
int value = _Id;
GetId(ref value);
- return (_Id = value);
+ return _Id = value;
}
+
protected set
{
int oldValue = _Id;
@@ -89,7 +93,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Path
+ /// Backing field for Path.
/// </summary>
protected string _Path;
/// <summary>
@@ -103,7 +107,7 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Required, Max length = 65535
- /// Absolute Path
+ /// Absolute Path.
/// </summary>
[Required]
[MaxLength(65535)]
@@ -114,8 +118,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Path;
GetPath(ref value);
- return (_Path = value);
+ return _Path = value;
}
+
set
{
string oldValue = _Path;
@@ -128,7 +133,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for NetworkPath
+ /// Backing field for NetworkPath.
/// </summary>
protected string _NetworkPath;
/// <summary>
@@ -152,8 +157,9 @@ namespace Jellyfin.Data.Entities
{
string value = _NetworkPath;
GetNetworkPath(ref value);
- return (_NetworkPath = value);
+ return _NetworkPath = value;
}
+
set
{
string oldValue = _NetworkPath;
@@ -166,7 +172,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrenyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -182,11 +188,10 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Required
+ /// Required.
/// </summary>
[ForeignKey("Library_Id")]
public virtual Library Library { get; set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/MediaFile.cs b/Jellyfin.Data/Entities/MediaFile.cs
index 719539e5c..7382cda95 100644
--- a/Jellyfin.Data/Entities/MediaFile.cs
+++ b/Jellyfin.Data/Entities/MediaFile.cs
@@ -28,19 +28,27 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
- /// <param name="path">Relative to the LibraryRoot</param>
+ /// <param name="path">Relative to the LibraryRoot.</param>
/// <param name="kind"></param>
/// <param name="_release0"></param>
public MediaFile(string path, Enums.MediaFileKind kind, Release _release0)
{
- if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path));
+ if (string.IsNullOrEmpty(path))
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
this.Path = path;
this.Kind = kind;
- if (_release0 == null) throw new ArgumentNullException(nameof(_release0));
+ if (_release0 == null)
+ {
+ throw new ArgumentNullException(nameof(_release0));
+ }
+
_release0.MediaFiles.Add(this);
this.MediaFileStreams = new HashSet<MediaFileStream>();
@@ -51,7 +59,7 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Static create function (for use in LINQ queries, etc.)
/// </summary>
- /// <param name="path">Relative to the LibraryRoot</param>
+ /// <param name="path">Relative to the LibraryRoot.</param>
/// <param name="kind"></param>
/// <param name="_release0"></param>
public static MediaFile Create(string path, Enums.MediaFileKind kind, Release _release0)
@@ -64,7 +72,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Id
+ /// Backing field for Id.
/// </summary>
internal int _Id;
/// <summary>
@@ -77,7 +85,7 @@ namespace Jellyfin.Data.Entities
partial void GetId(ref int result);
/// <summary>
- /// Identity, Indexed, Required
+ /// Identity, Indexed, Required.
/// </summary>
[Key]
[Required]
@@ -88,8 +96,9 @@ namespace Jellyfin.Data.Entities
{
int value = _Id;
GetId(ref value);
- return (_Id = value);
+ return _Id = value;
}
+
protected set
{
int oldValue = _Id;
@@ -102,7 +111,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Path
+ /// Backing field for Path.
/// </summary>
protected string _Path;
/// <summary>
@@ -116,7 +125,7 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Required, Max length = 65535
- /// Relative to the LibraryRoot
+ /// Relative to the LibraryRoot.
/// </summary>
[Required]
[MaxLength(65535)]
@@ -127,8 +136,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Path;
GetPath(ref value);
- return (_Path = value);
+ return _Path = value;
}
+
set
{
string oldValue = _Path;
@@ -141,7 +151,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Kind
+ /// Backing field for Kind.
/// </summary>
protected Enums.MediaFileKind _Kind;
/// <summary>
@@ -154,7 +164,7 @@ namespace Jellyfin.Data.Entities
partial void GetKind(ref Enums.MediaFileKind result);
/// <summary>
- /// Required
+ /// Required.
/// </summary>
[Required]
public Enums.MediaFileKind Kind
@@ -163,8 +173,9 @@ namespace Jellyfin.Data.Entities
{
Enums.MediaFileKind value = _Kind;
GetKind(ref value);
- return (_Kind = value);
+ return _Kind = value;
}
+
set
{
Enums.MediaFileKind oldValue = _Kind;
@@ -177,7 +188,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrenyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -194,7 +205,6 @@ namespace Jellyfin.Data.Entities
[ForeignKey("MediaFileStream_MediaFileStreams_Id")]
public virtual ICollection<MediaFileStream> MediaFileStreams { get; protected set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/MediaFileStream.cs b/Jellyfin.Data/Entities/MediaFileStream.cs
index 7b3399731..977fd54e1 100644
--- a/Jellyfin.Data/Entities/MediaFileStream.cs
+++ b/Jellyfin.Data/Entities/MediaFileStream.cs
@@ -25,7 +25,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="streamnumber"></param>
/// <param name="_mediafile0"></param>
@@ -33,9 +33,12 @@ namespace Jellyfin.Data.Entities
{
this.StreamNumber = streamnumber;
- if (_mediafile0 == null) throw new ArgumentNullException(nameof(_mediafile0));
- _mediafile0.MediaFileStreams.Add(this);
+ if (_mediafile0 == null)
+ {
+ throw new ArgumentNullException(nameof(_mediafile0));
+ }
+ _mediafile0.MediaFileStreams.Add(this);
Init();
}
@@ -55,7 +58,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Id
+ /// Backing field for Id.
/// </summary>
internal int _Id;
/// <summary>
@@ -68,7 +71,7 @@ namespace Jellyfin.Data.Entities
partial void GetId(ref int result);
/// <summary>
- /// Identity, Indexed, Required
+ /// Identity, Indexed, Required.
/// </summary>
[Key]
[Required]
@@ -79,8 +82,9 @@ namespace Jellyfin.Data.Entities
{
int value = _Id;
GetId(ref value);
- return (_Id = value);
+ return _Id = value;
}
+
protected set
{
int oldValue = _Id;
@@ -93,7 +97,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for StreamNumber
+ /// Backing field for StreamNumber.
/// </summary>
protected int _StreamNumber;
/// <summary>
@@ -106,7 +110,7 @@ namespace Jellyfin.Data.Entities
partial void GetStreamNumber(ref int result);
/// <summary>
- /// Required
+ /// Required.
/// </summary>
[Required]
public int StreamNumber
@@ -115,8 +119,9 @@ namespace Jellyfin.Data.Entities
{
int value = _StreamNumber;
GetStreamNumber(ref value);
- return (_StreamNumber = value);
+ return _StreamNumber = value;
}
+
set
{
int oldValue = _StreamNumber;
@@ -129,7 +134,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrenyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -143,7 +148,6 @@ namespace Jellyfin.Data.Entities
/*************************************************************************
* Navigation properties
*************************************************************************/
-
}
}
diff --git a/Jellyfin.Data/Entities/Metadata.cs b/Jellyfin.Data/Entities/Metadata.cs
index 467ee6822..a4ac6dc54 100644
--- a/Jellyfin.Data/Entities/Metadata.cs
+++ b/Jellyfin.Data/Entities/Metadata.cs
@@ -24,16 +24,24 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
- /// <param name="title">The title or name of the object</param>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="title">The title or name of the object.</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
protected Metadata(string title, string language, DateTime dateadded, DateTime datemodified)
{
- if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ if (string.IsNullOrEmpty(title))
+ {
+ throw new ArgumentNullException(nameof(title));
+ }
+
this.Title = title;
- if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ if (string.IsNullOrEmpty(language))
+ {
+ throw new ArgumentNullException(nameof(language));
+ }
+
this.Language = language;
this.PersonRoles = new HashSet<PersonRole>();
@@ -50,7 +58,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Id
+ /// Backing field for Id.
/// </summary>
internal int _Id;
/// <summary>
@@ -63,7 +71,7 @@ namespace Jellyfin.Data.Entities
partial void GetId(ref int result);
/// <summary>
- /// Identity, Indexed, Required
+ /// Identity, Indexed, Required.
/// </summary>
[Key]
[Required]
@@ -74,8 +82,9 @@ namespace Jellyfin.Data.Entities
{
int value = _Id;
GetId(ref value);
- return (_Id = value);
+ return _Id = value;
}
+
protected set
{
int oldValue = _Id;
@@ -88,7 +97,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Title
+ /// Backing field for Title.
/// </summary>
protected string _Title;
/// <summary>
@@ -102,7 +111,7 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Required, Max length = 1024
- /// The title or name of the object
+ /// The title or name of the object.
/// </summary>
[Required]
[MaxLength(1024)]
@@ -113,8 +122,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Title;
GetTitle(ref value);
- return (_Title = value);
+ return _Title = value;
}
+
set
{
string oldValue = _Title;
@@ -127,7 +137,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for OriginalTitle
+ /// Backing field for OriginalTitle.
/// </summary>
protected string _OriginalTitle;
/// <summary>
@@ -150,8 +160,9 @@ namespace Jellyfin.Data.Entities
{
string value = _OriginalTitle;
GetOriginalTitle(ref value);
- return (_OriginalTitle = value);
+ return _OriginalTitle = value;
}
+
set
{
string oldValue = _OriginalTitle;
@@ -164,7 +175,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for SortTitle
+ /// Backing field for SortTitle.
/// </summary>
protected string _SortTitle;
/// <summary>
@@ -187,8 +198,9 @@ namespace Jellyfin.Data.Entities
{
string value = _SortTitle;
GetSortTitle(ref value);
- return (_SortTitle = value);
+ return _SortTitle = value;
}
+
set
{
string oldValue = _SortTitle;
@@ -201,7 +213,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Language
+ /// Backing field for Language.
/// </summary>
protected string _Language;
/// <summary>
@@ -215,7 +227,7 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Required, Min length = 3, Max length = 3
- /// ISO-639-3 3-character language codes
+ /// ISO-639-3 3-character language codes.
/// </summary>
[Required]
[MinLength(3)]
@@ -227,8 +239,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Language;
GetLanguage(ref value);
- return (_Language = value);
+ return _Language = value;
}
+
set
{
string oldValue = _Language;
@@ -241,7 +254,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for ReleaseDate
+ /// Backing field for ReleaseDate.
/// </summary>
protected DateTimeOffset? _ReleaseDate;
/// <summary>
@@ -259,8 +272,9 @@ namespace Jellyfin.Data.Entities
{
DateTimeOffset? value = _ReleaseDate;
GetReleaseDate(ref value);
- return (_ReleaseDate = value);
+ return _ReleaseDate = value;
}
+
set
{
DateTimeOffset? oldValue = _ReleaseDate;
@@ -273,7 +287,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for DateAdded
+ /// Backing field for DateAdded.
/// </summary>
protected DateTime _DateAdded;
/// <summary>
@@ -286,7 +300,7 @@ namespace Jellyfin.Data.Entities
partial void GetDateAdded(ref DateTime result);
/// <summary>
- /// Required
+ /// Required.
/// </summary>
[Required]
public DateTime DateAdded
@@ -295,8 +309,9 @@ namespace Jellyfin.Data.Entities
{
DateTime value = _DateAdded;
GetDateAdded(ref value);
- return (_DateAdded = value);
+ return _DateAdded = value;
}
+
internal set
{
DateTime oldValue = _DateAdded;
@@ -309,7 +324,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for DateModified
+ /// Backing field for DateModified.
/// </summary>
protected DateTime _DateModified;
/// <summary>
@@ -322,7 +337,7 @@ namespace Jellyfin.Data.Entities
partial void GetDateModified(ref DateTime result);
/// <summary>
- /// Required
+ /// Required.
/// </summary>
[Required]
public DateTime DateModified
@@ -331,8 +346,9 @@ namespace Jellyfin.Data.Entities
{
DateTime value = _DateModified;
GetDateModified(ref value);
- return (_DateModified = value);
+ return _DateModified = value;
}
+
internal set
{
DateTime oldValue = _DateModified;
@@ -345,7 +361,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrenyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -374,7 +390,6 @@ namespace Jellyfin.Data.Entities
[ForeignKey("PersonRole_PersonRoles_Id")]
public virtual ICollection<MetadataProviderId> Sources { get; protected set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/MetadataProvider.cs b/Jellyfin.Data/Entities/MetadataProvider.cs
index 4e4f107fb..e93ea97d6 100644
--- a/Jellyfin.Data/Entities/MetadataProvider.cs
+++ b/Jellyfin.Data/Entities/MetadataProvider.cs
@@ -25,14 +25,17 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="name"></param>
public MetadataProvider(string name)
{
- if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
- this.Name = name;
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+ this.Name = name;
Init();
}
@@ -51,7 +54,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Id
+ /// Backing field for Id.
/// </summary>
internal int _Id;
/// <summary>
@@ -64,7 +67,7 @@ namespace Jellyfin.Data.Entities
partial void GetId(ref int result);
/// <summary>
- /// Identity, Indexed, Required
+ /// Identity, Indexed, Required.
/// </summary>
[Key]
[Required]
@@ -75,8 +78,9 @@ namespace Jellyfin.Data.Entities
{
int value = _Id;
GetId(ref value);
- return (_Id = value);
+ return _Id = value;
}
+
protected set
{
int oldValue = _Id;
@@ -89,7 +93,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Name
+ /// Backing field for Name.
/// </summary>
protected string _Name;
/// <summary>
@@ -113,8 +117,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Name;
GetName(ref value);
- return (_Name = value);
+ return _Name = value;
}
+
set
{
string oldValue = _Name;
@@ -127,7 +132,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrenyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -141,7 +146,6 @@ namespace Jellyfin.Data.Entities
/*************************************************************************
* Navigation properties
*************************************************************************/
-
}
}
diff --git a/Jellyfin.Data/Entities/MetadataProviderId.cs b/Jellyfin.Data/Entities/MetadataProviderId.cs
index 926f223de..68f139436 100644
--- a/Jellyfin.Data/Entities/MetadataProviderId.cs
+++ b/Jellyfin.Data/Entities/MetadataProviderId.cs
@@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="providerid"></param>
/// <param name="_metadata0"></param>
@@ -40,21 +40,40 @@ namespace Jellyfin.Data.Entities
// NOTE: This class has one-to-one associations with MetadataProviderId.
// One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other.
- if (string.IsNullOrEmpty(providerid)) throw new ArgumentNullException(nameof(providerid));
+ if (string.IsNullOrEmpty(providerid))
+ {
+ throw new ArgumentNullException(nameof(providerid));
+ }
+
this.ProviderId = providerid;
- if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0));
+ if (_metadata0 == null)
+ {
+ throw new ArgumentNullException(nameof(_metadata0));
+ }
+
_metadata0.Sources.Add(this);
- if (_person1 == null) throw new ArgumentNullException(nameof(_person1));
+ if (_person1 == null)
+ {
+ throw new ArgumentNullException(nameof(_person1));
+ }
+
_person1.Sources.Add(this);
- if (_personrole2 == null) throw new ArgumentNullException(nameof(_personrole2));
+ if (_personrole2 == null)
+ {
+ throw new ArgumentNullException(nameof(_personrole2));
+ }
+
_personrole2.Sources.Add(this);
- if (_ratingsource3 == null) throw new ArgumentNullException(nameof(_ratingsource3));
- _ratingsource3.Source = this;
+ if (_ratingsource3 == null)
+ {
+ throw new ArgumentNullException(nameof(_ratingsource3));
+ }
+ _ratingsource3.Source = this;
Init();
}
@@ -77,7 +96,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Id
+ /// Backing field for Id.
/// </summary>
internal int _Id;
/// <summary>
@@ -90,7 +109,7 @@ namespace Jellyfin.Data.Entities
partial void GetId(ref int result);
/// <summary>
- /// Identity, Indexed, Required
+ /// Identity, Indexed, Required.
/// </summary>
[Key]
[Required]
@@ -101,8 +120,9 @@ namespace Jellyfin.Data.Entities
{
int value = _Id;
GetId(ref value);
- return (_Id = value);
+ return _Id = value;
}
+
protected set
{
int oldValue = _Id;
@@ -115,7 +135,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for ProviderId
+ /// Backing field for ProviderId.
/// </summary>
protected string _ProviderId;
/// <summary>
@@ -139,8 +159,9 @@ namespace Jellyfin.Data.Entities
{
string value = _ProviderId;
GetProviderId(ref value);
- return (_ProviderId = value);
+ return _ProviderId = value;
}
+
set
{
string oldValue = _ProviderId;
@@ -153,7 +174,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrenyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -169,11 +190,10 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Required
+ /// Required.
/// </summary>
[ForeignKey("MetadataProvider_Id")]
public virtual MetadataProvider MetadataProvider { get; set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/Movie.cs b/Jellyfin.Data/Entities/Movie.cs
index b359b42fc..64326ca3a 100644
--- a/Jellyfin.Data/Entities/Movie.cs
+++ b/Jellyfin.Data/Entities/Movie.cs
@@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
public Movie(Guid urlid, DateTime dateadded)
@@ -63,7 +63,6 @@ namespace Jellyfin.Data.Entities
[ForeignKey("MovieMetadata_MovieMetadata_Id")]
public virtual ICollection<MovieMetadata> MovieMetadata { get; protected set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/MovieMetadata.cs b/Jellyfin.Data/Entities/MovieMetadata.cs
index 319ae94e5..cbcb78e37 100644
--- a/Jellyfin.Data/Entities/MovieMetadata.cs
+++ b/Jellyfin.Data/Entities/MovieMetadata.cs
@@ -28,20 +28,32 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
- /// <param name="title">The title or name of the object</param>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="title">The title or name of the object.</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="_movie0"></param>
public MovieMetadata(string title, string language, DateTime dateadded, DateTime datemodified, Movie _movie0)
{
- if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ if (string.IsNullOrEmpty(title))
+ {
+ throw new ArgumentNullException(nameof(title));
+ }
+
this.Title = title;
- if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ if (string.IsNullOrEmpty(language))
+ {
+ throw new ArgumentNullException(nameof(language));
+ }
+
this.Language = language;
- if (_movie0 == null) throw new ArgumentNullException(nameof(_movie0));
+ if (_movie0 == null)
+ {
+ throw new ArgumentNullException(nameof(_movie0));
+ }
+
_movie0.MovieMetadata.Add(this);
this.Studios = new HashSet<Company>();
@@ -52,8 +64,8 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Static create function (for use in LINQ queries, etc.)
/// </summary>
- /// <param name="title">The title or name of the object</param>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="title">The title or name of the object.</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="_movie0"></param>
public static MovieMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, Movie _movie0)
{
@@ -65,7 +77,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Outline
+ /// Backing field for Outline.
/// </summary>
protected string _Outline;
/// <summary>
@@ -88,8 +100,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Outline;
GetOutline(ref value);
- return (_Outline = value);
+ return _Outline = value;
}
+
set
{
string oldValue = _Outline;
@@ -102,7 +115,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Plot
+ /// Backing field for Plot.
/// </summary>
protected string _Plot;
/// <summary>
@@ -125,8 +138,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Plot;
GetPlot(ref value);
- return (_Plot = value);
+ return _Plot = value;
}
+
set
{
string oldValue = _Plot;
@@ -139,7 +153,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Tagline
+ /// Backing field for Tagline.
/// </summary>
protected string _Tagline;
/// <summary>
@@ -162,8 +176,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Tagline;
GetTagline(ref value);
- return (_Tagline = value);
+ return _Tagline = value;
}
+
set
{
string oldValue = _Tagline;
@@ -176,7 +191,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Country
+ /// Backing field for Country.
/// </summary>
protected string _Country;
/// <summary>
@@ -199,8 +214,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Country;
GetCountry(ref value);
- return (_Country = value);
+ return _Country = value;
}
+
set
{
string oldValue = _Country;
@@ -217,7 +233,6 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
[ForeignKey("Company_Studios_Id")]
public virtual ICollection<Company> Studios { get; protected set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/MusicAlbum.cs b/Jellyfin.Data/Entities/MusicAlbum.cs
index 00cb8fe00..9afea1fb6 100644
--- a/Jellyfin.Data/Entities/MusicAlbum.cs
+++ b/Jellyfin.Data/Entities/MusicAlbum.cs
@@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
public MusicAlbum(Guid urlid, DateTime dateadded)
@@ -62,7 +62,6 @@ namespace Jellyfin.Data.Entities
[ForeignKey("Track_Tracks_Id")]
public virtual ICollection<Track> Tracks { get; protected set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/MusicAlbumMetadata.cs b/Jellyfin.Data/Entities/MusicAlbumMetadata.cs
index b52ca6564..bfcbebbe8 100644
--- a/Jellyfin.Data/Entities/MusicAlbumMetadata.cs
+++ b/Jellyfin.Data/Entities/MusicAlbumMetadata.cs
@@ -28,20 +28,32 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
- /// <param name="title">The title or name of the object</param>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="title">The title or name of the object.</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="_musicalbum0"></param>
public MusicAlbumMetadata(string title, string language, DateTime dateadded, DateTime datemodified, MusicAlbum _musicalbum0)
{
- if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ if (string.IsNullOrEmpty(title))
+ {
+ throw new ArgumentNullException(nameof(title));
+ }
+
this.Title = title;
- if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ if (string.IsNullOrEmpty(language))
+ {
+ throw new ArgumentNullException(nameof(language));
+ }
+
this.Language = language;
- if (_musicalbum0 == null) throw new ArgumentNullException(nameof(_musicalbum0));
+ if (_musicalbum0 == null)
+ {
+ throw new ArgumentNullException(nameof(_musicalbum0));
+ }
+
_musicalbum0.MusicAlbumMetadata.Add(this);
this.Labels = new HashSet<Company>();
@@ -52,8 +64,8 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Static create function (for use in LINQ queries, etc.)
/// </summary>
- /// <param name="title">The title or name of the object</param>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="title">The title or name of the object.</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="_musicalbum0"></param>
public static MusicAlbumMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, MusicAlbum _musicalbum0)
{
@@ -65,7 +77,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Barcode
+ /// Backing field for Barcode.
/// </summary>
protected string _Barcode;
/// <summary>
@@ -88,8 +100,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Barcode;
GetBarcode(ref value);
- return (_Barcode = value);
+ return _Barcode = value;
}
+
set
{
string oldValue = _Barcode;
@@ -102,7 +115,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for LabelNumber
+ /// Backing field for LabelNumber.
/// </summary>
protected string _LabelNumber;
/// <summary>
@@ -125,8 +138,9 @@ namespace Jellyfin.Data.Entities
{
string value = _LabelNumber;
GetLabelNumber(ref value);
- return (_LabelNumber = value);
+ return _LabelNumber = value;
}
+
set
{
string oldValue = _LabelNumber;
@@ -139,7 +153,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Country
+ /// Backing field for Country.
/// </summary>
protected string _Country;
/// <summary>
@@ -162,8 +176,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Country;
GetCountry(ref value);
- return (_Country = value);
+ return _Country = value;
}
+
set
{
string oldValue = _Country;
@@ -181,7 +196,6 @@ namespace Jellyfin.Data.Entities
[ForeignKey("Company_Labels_Id")]
public virtual ICollection<Company> Labels { get; protected set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/Person.cs b/Jellyfin.Data/Entities/Person.cs
index d893b7e39..b6d91ea86 100644
--- a/Jellyfin.Data/Entities/Person.cs
+++ b/Jellyfin.Data/Entities/Person.cs
@@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="urlid"></param>
/// <param name="name"></param>
@@ -36,7 +36,11 @@ namespace Jellyfin.Data.Entities
{
this.UrlId = urlid;
- if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+
this.Name = name;
this.Sources = new HashSet<MetadataProviderId>();
@@ -59,7 +63,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Id
+ /// Backing field for Id.
/// </summary>
internal int _Id;
/// <summary>
@@ -72,7 +76,7 @@ namespace Jellyfin.Data.Entities
partial void GetId(ref int result);
/// <summary>
- /// Identity, Indexed, Required
+ /// Identity, Indexed, Required.
/// </summary>
[Key]
[Required]
@@ -83,8 +87,9 @@ namespace Jellyfin.Data.Entities
{
int value = _Id;
GetId(ref value);
- return (_Id = value);
+ return _Id = value;
}
+
protected set
{
int oldValue = _Id;
@@ -97,7 +102,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for UrlId
+ /// Backing field for UrlId.
/// </summary>
protected Guid _UrlId;
/// <summary>
@@ -110,7 +115,7 @@ namespace Jellyfin.Data.Entities
partial void GetUrlId(ref Guid result);
/// <summary>
- /// Required
+ /// Required.
/// </summary>
[Required]
public Guid UrlId
@@ -119,8 +124,9 @@ namespace Jellyfin.Data.Entities
{
Guid value = _UrlId;
GetUrlId(ref value);
- return (_UrlId = value);
+ return _UrlId = value;
}
+
set
{
Guid oldValue = _UrlId;
@@ -133,7 +139,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Name
+ /// Backing field for Name.
/// </summary>
protected string _Name;
/// <summary>
@@ -157,8 +163,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Name;
GetName(ref value);
- return (_Name = value);
+ return _Name = value;
}
+
set
{
string oldValue = _Name;
@@ -171,7 +178,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for SourceId
+ /// Backing field for SourceId.
/// </summary>
protected string _SourceId;
/// <summary>
@@ -194,8 +201,9 @@ namespace Jellyfin.Data.Entities
{
string value = _SourceId;
GetSourceId(ref value);
- return (_SourceId = value);
+ return _SourceId = value;
}
+
set
{
string oldValue = _SourceId;
@@ -208,7 +216,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for DateAdded
+ /// Backing field for DateAdded.
/// </summary>
protected DateTime _DateAdded;
/// <summary>
@@ -221,7 +229,7 @@ namespace Jellyfin.Data.Entities
partial void GetDateAdded(ref DateTime result);
/// <summary>
- /// Required
+ /// Required.
/// </summary>
[Required]
public DateTime DateAdded
@@ -230,8 +238,9 @@ namespace Jellyfin.Data.Entities
{
DateTime value = _DateAdded;
GetDateAdded(ref value);
- return (_DateAdded = value);
+ return _DateAdded = value;
}
+
internal set
{
DateTime oldValue = _DateAdded;
@@ -244,7 +253,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for DateModified
+ /// Backing field for DateModified.
/// </summary>
protected DateTime _DateModified;
/// <summary>
@@ -257,7 +266,7 @@ namespace Jellyfin.Data.Entities
partial void GetDateModified(ref DateTime result);
/// <summary>
- /// Required
+ /// Required.
/// </summary>
[Required]
public DateTime DateModified
@@ -266,8 +275,9 @@ namespace Jellyfin.Data.Entities
{
DateTime value = _DateModified;
GetDateModified(ref value);
- return (_DateModified = value);
+ return _DateModified = value;
}
+
internal set
{
DateTime oldValue = _DateModified;
@@ -280,7 +290,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrenyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -296,7 +306,6 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
[ForeignKey("MetadataProviderId_Sources_Id")]
public virtual ICollection<MetadataProviderId> Sources { get; protected set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/PersonRole.cs b/Jellyfin.Data/Entities/PersonRole.cs
index 9bd12c7fb..2dd5f116f 100644
--- a/Jellyfin.Data/Entities/PersonRole.cs
+++ b/Jellyfin.Data/Entities/PersonRole.cs
@@ -31,7 +31,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="type"></param>
/// <param name="_metadata0"></param>
@@ -42,7 +42,11 @@ namespace Jellyfin.Data.Entities
this.Type = type;
- if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0));
+ if (_metadata0 == null)
+ {
+ throw new ArgumentNullException(nameof(_metadata0));
+ }
+
_metadata0.PersonRoles.Add(this);
this.Sources = new HashSet<MetadataProviderId>();
@@ -65,7 +69,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Id
+ /// Backing field for Id.
/// </summary>
internal int _Id;
/// <summary>
@@ -78,7 +82,7 @@ namespace Jellyfin.Data.Entities
partial void GetId(ref int result);
/// <summary>
- /// Identity, Indexed, Required
+ /// Identity, Indexed, Required.
/// </summary>
[Key]
[Required]
@@ -89,8 +93,9 @@ namespace Jellyfin.Data.Entities
{
int value = _Id;
GetId(ref value);
- return (_Id = value);
+ return _Id = value;
}
+
protected set
{
int oldValue = _Id;
@@ -103,7 +108,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Role
+ /// Backing field for Role.
/// </summary>
protected string _Role;
/// <summary>
@@ -126,8 +131,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Role;
GetRole(ref value);
- return (_Role = value);
+ return _Role = value;
}
+
set
{
string oldValue = _Role;
@@ -140,7 +146,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Type
+ /// Backing field for Type.
/// </summary>
protected Enums.PersonRoleType _Type;
/// <summary>
@@ -153,7 +159,7 @@ namespace Jellyfin.Data.Entities
partial void GetType(ref Enums.PersonRoleType result);
/// <summary>
- /// Required
+ /// Required.
/// </summary>
[Required]
public Enums.PersonRoleType Type
@@ -162,8 +168,9 @@ namespace Jellyfin.Data.Entities
{
Enums.PersonRoleType value = _Type;
GetType(ref value);
- return (_Type = value);
+ return _Type = value;
}
+
set
{
Enums.PersonRoleType oldValue = _Type;
@@ -176,7 +183,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrenyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -192,7 +199,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Required
+ /// Required.
/// </summary>
[ForeignKey("Person_Id")]
@@ -203,7 +210,6 @@ namespace Jellyfin.Data.Entities
[ForeignKey("MetadataProviderId_Sources_Id")]
public virtual ICollection<MetadataProviderId> Sources { get; protected set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/Photo.cs b/Jellyfin.Data/Entities/Photo.cs
index 7abe62891..9da55fe43 100644
--- a/Jellyfin.Data/Entities/Photo.cs
+++ b/Jellyfin.Data/Entities/Photo.cs
@@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
public Photo(Guid urlid, DateTime dateadded)
@@ -62,7 +62,6 @@ namespace Jellyfin.Data.Entities
[ForeignKey("Release_Releases_Id")]
public virtual ICollection<Release> Releases { get; protected set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/PhotoMetadata.cs b/Jellyfin.Data/Entities/PhotoMetadata.cs
index c5502f707..b5aec7229 100644
--- a/Jellyfin.Data/Entities/PhotoMetadata.cs
+++ b/Jellyfin.Data/Entities/PhotoMetadata.cs
@@ -24,22 +24,33 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
- /// <param name="title">The title or name of the object</param>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="title">The title or name of the object.</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="_photo0"></param>
public PhotoMetadata(string title, string language, DateTime dateadded, DateTime datemodified, Photo _photo0)
{
- if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ if (string.IsNullOrEmpty(title))
+ {
+ throw new ArgumentNullException(nameof(title));
+ }
+
this.Title = title;
- if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ if (string.IsNullOrEmpty(language))
+ {
+ throw new ArgumentNullException(nameof(language));
+ }
+
this.Language = language;
- if (_photo0 == null) throw new ArgumentNullException(nameof(_photo0));
- _photo0.PhotoMetadata.Add(this);
+ if (_photo0 == null)
+ {
+ throw new ArgumentNullException(nameof(_photo0));
+ }
+ _photo0.PhotoMetadata.Add(this);
Init();
}
@@ -47,8 +58,8 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Static create function (for use in LINQ queries, etc.)
/// </summary>
- /// <param name="title">The title or name of the object</param>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="title">The title or name of the object.</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="_photo0"></param>
public static PhotoMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, Photo _photo0)
{
@@ -62,7 +73,6 @@ namespace Jellyfin.Data.Entities
/*************************************************************************
* Navigation properties
*************************************************************************/
-
}
}
diff --git a/Jellyfin.Data/Entities/ProviderMapping.cs b/Jellyfin.Data/Entities/ProviderMapping.cs
index e479341ad..c53e3bf40 100644
--- a/Jellyfin.Data/Entities/ProviderMapping.cs
+++ b/Jellyfin.Data/Entities/ProviderMapping.cs
@@ -25,7 +25,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="providername"></param>
/// <param name="providersecrets"></param>
@@ -34,15 +34,26 @@ namespace Jellyfin.Data.Entities
/// <param name="_group1"></param>
public ProviderMapping(string providername, string providersecrets, string providerdata, User _user0, Group _group1)
{
- if (string.IsNullOrEmpty(providername)) throw new ArgumentNullException(nameof(providername));
+ if (string.IsNullOrEmpty(providername))
+ {
+ throw new ArgumentNullException(nameof(providername));
+ }
+
this.ProviderName = providername;
- if (string.IsNullOrEmpty(providersecrets)) throw new ArgumentNullException(nameof(providersecrets));
+ if (string.IsNullOrEmpty(providersecrets))
+ {
+ throw new ArgumentNullException(nameof(providersecrets));
+ }
+
this.ProviderSecrets = providersecrets;
- if (string.IsNullOrEmpty(providerdata)) throw new ArgumentNullException(nameof(providerdata));
- this.ProviderData = providerdata;
+ if (string.IsNullOrEmpty(providerdata))
+ {
+ throw new ArgumentNullException(nameof(providerdata));
+ }
+ this.ProviderData = providerdata;
Init();
}
@@ -65,7 +76,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Identity, Indexed, Required
+ /// Identity, Indexed, Required.
/// </summary>
[Key]
[Required]
@@ -97,7 +108,7 @@ namespace Jellyfin.Data.Entities
public string ProviderData { get; set; }
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrenyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -111,7 +122,6 @@ namespace Jellyfin.Data.Entities
/*************************************************************************
* Navigation properties
*************************************************************************/
-
}
}
diff --git a/Jellyfin.Data/Entities/Rating.cs b/Jellyfin.Data/Entities/Rating.cs
index f70ea8b33..49a0d502d 100644
--- a/Jellyfin.Data/Entities/Rating.cs
+++ b/Jellyfin.Data/Entities/Rating.cs
@@ -25,7 +25,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="value"></param>
/// <param name="_metadata0"></param>
@@ -33,9 +33,12 @@ namespace Jellyfin.Data.Entities
{
this.Value = value;
- if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0));
- _metadata0.Ratings.Add(this);
+ if (_metadata0 == null)
+ {
+ throw new ArgumentNullException(nameof(_metadata0));
+ }
+ _metadata0.Ratings.Add(this);
Init();
}
@@ -55,7 +58,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Id
+ /// Backing field for Id.
/// </summary>
internal int _Id;
/// <summary>
@@ -68,7 +71,7 @@ namespace Jellyfin.Data.Entities
partial void GetId(ref int result);
/// <summary>
- /// Identity, Indexed, Required
+ /// Identity, Indexed, Required.
/// </summary>
[Key]
[Required]
@@ -79,8 +82,9 @@ namespace Jellyfin.Data.Entities
{
int value = _Id;
GetId(ref value);
- return (_Id = value);
+ return _Id = value;
}
+
protected set
{
int oldValue = _Id;
@@ -93,7 +97,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Value
+ /// Backing field for Value.
/// </summary>
protected double _Value;
/// <summary>
@@ -106,7 +110,7 @@ namespace Jellyfin.Data.Entities
partial void GetValue(ref double result);
/// <summary>
- /// Required
+ /// Required.
/// </summary>
[Required]
public double Value
@@ -115,8 +119,9 @@ namespace Jellyfin.Data.Entities
{
double value = _Value;
GetValue(ref value);
- return (_Value = value);
+ return _Value = value;
}
+
set
{
double oldValue = _Value;
@@ -129,7 +134,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Votes
+ /// Backing field for Votes.
/// </summary>
protected int? _Votes;
/// <summary>
@@ -147,8 +152,9 @@ namespace Jellyfin.Data.Entities
{
int? value = _Votes;
GetVotes(ref value);
- return (_Votes = value);
+ return _Votes = value;
}
+
set
{
int? oldValue = _Votes;
@@ -161,7 +167,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrenyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -181,7 +187,6 @@ namespace Jellyfin.Data.Entities
/// </summary>
[ForeignKey("RatingSource_RatingType_Id")]
public virtual RatingSource RatingType { get; set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/RatingSource.cs b/Jellyfin.Data/Entities/RatingSource.cs
index 070f1ae27..b62d8b444 100644
--- a/Jellyfin.Data/Entities/RatingSource.cs
+++ b/Jellyfin.Data/Entities/RatingSource.cs
@@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Jellyfin.Data.Entities
{
/// <summary>
- /// This is the entity to store review ratings, not age ratings
+ /// This is the entity to store review ratings, not age ratings.
/// </summary>
public partial class RatingSource
{
@@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="maximumvalue"></param>
/// <param name="minimumvalue"></param>
@@ -39,9 +39,12 @@ namespace Jellyfin.Data.Entities
this.MinimumValue = minimumvalue;
- if (_rating0 == null) throw new ArgumentNullException(nameof(_rating0));
- _rating0.RatingType = this;
+ if (_rating0 == null)
+ {
+ throw new ArgumentNullException(nameof(_rating0));
+ }
+ _rating0.RatingType = this;
Init();
}
@@ -62,7 +65,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Id
+ /// Backing field for Id.
/// </summary>
internal int _Id;
/// <summary>
@@ -75,7 +78,7 @@ namespace Jellyfin.Data.Entities
partial void GetId(ref int result);
/// <summary>
- /// Identity, Indexed, Required
+ /// Identity, Indexed, Required.
/// </summary>
[Key]
[Required]
@@ -86,8 +89,9 @@ namespace Jellyfin.Data.Entities
{
int value = _Id;
GetId(ref value);
- return (_Id = value);
+ return _Id = value;
}
+
protected set
{
int oldValue = _Id;
@@ -100,7 +104,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Name
+ /// Backing field for Name.
/// </summary>
protected string _Name;
/// <summary>
@@ -123,8 +127,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Name;
GetName(ref value);
- return (_Name = value);
+ return _Name = value;
}
+
set
{
string oldValue = _Name;
@@ -137,7 +142,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for MaximumValue
+ /// Backing field for MaximumValue.
/// </summary>
protected double _MaximumValue;
/// <summary>
@@ -150,7 +155,7 @@ namespace Jellyfin.Data.Entities
partial void GetMaximumValue(ref double result);
/// <summary>
- /// Required
+ /// Required.
/// </summary>
[Required]
public double MaximumValue
@@ -159,8 +164,9 @@ namespace Jellyfin.Data.Entities
{
double value = _MaximumValue;
GetMaximumValue(ref value);
- return (_MaximumValue = value);
+ return _MaximumValue = value;
}
+
set
{
double oldValue = _MaximumValue;
@@ -173,7 +179,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for MinimumValue
+ /// Backing field for MinimumValue.
/// </summary>
protected double _MinimumValue;
/// <summary>
@@ -186,7 +192,7 @@ namespace Jellyfin.Data.Entities
partial void GetMinimumValue(ref double result);
/// <summary>
- /// Required
+ /// Required.
/// </summary>
[Required]
public double MinimumValue
@@ -195,8 +201,9 @@ namespace Jellyfin.Data.Entities
{
double value = _MinimumValue;
GetMinimumValue(ref value);
- return (_MinimumValue = value);
+ return _MinimumValue = value;
}
+
set
{
double oldValue = _MinimumValue;
@@ -209,7 +216,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrenyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -225,7 +232,6 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
[ForeignKey("MetadataProviderId_Source_Id")]
public virtual MetadataProviderId Source { get; set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/Release.cs b/Jellyfin.Data/Entities/Release.cs
index d1928fcf7..1e9faa5a1 100644
--- a/Jellyfin.Data/Entities/Release.cs
+++ b/Jellyfin.Data/Entities/Release.cs
@@ -29,7 +29,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="name"></param>
/// <param name="_movie0"></param>
@@ -40,25 +40,53 @@ namespace Jellyfin.Data.Entities
/// <param name="_photo5"></param>
public Release(string name, Movie _movie0, Episode _episode1, Track _track2, CustomItem _customitem3, Book _book4, Photo _photo5)
{
- if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+
this.Name = name;
- if (_movie0 == null) throw new ArgumentNullException(nameof(_movie0));
+ if (_movie0 == null)
+ {
+ throw new ArgumentNullException(nameof(_movie0));
+ }
+
_movie0.Releases.Add(this);
- if (_episode1 == null) throw new ArgumentNullException(nameof(_episode1));
+ if (_episode1 == null)
+ {
+ throw new ArgumentNullException(nameof(_episode1));
+ }
+
_episode1.Releases.Add(this);
- if (_track2 == null) throw new ArgumentNullException(nameof(_track2));
+ if (_track2 == null)
+ {
+ throw new ArgumentNullException(nameof(_track2));
+ }
+
_track2.Releases.Add(this);
- if (_customitem3 == null) throw new ArgumentNullException(nameof(_customitem3));
+ if (_customitem3 == null)
+ {
+ throw new ArgumentNullException(nameof(_customitem3));
+ }
+
_customitem3.Releases.Add(this);
- if (_book4 == null) throw new ArgumentNullException(nameof(_book4));
+ if (_book4 == null)
+ {
+ throw new ArgumentNullException(nameof(_book4));
+ }
+
_book4.Releases.Add(this);
- if (_photo5 == null) throw new ArgumentNullException(nameof(_photo5));
+ if (_photo5 == null)
+ {
+ throw new ArgumentNullException(nameof(_photo5));
+ }
+
_photo5.Releases.Add(this);
this.MediaFiles = new HashSet<MediaFile>();
@@ -87,7 +115,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Id
+ /// Backing field for Id.
/// </summary>
internal int _Id;
/// <summary>
@@ -100,7 +128,7 @@ namespace Jellyfin.Data.Entities
partial void GetId(ref int result);
/// <summary>
- /// Identity, Indexed, Required
+ /// Identity, Indexed, Required.
/// </summary>
[Key]
[Required]
@@ -111,8 +139,9 @@ namespace Jellyfin.Data.Entities
{
int value = _Id;
GetId(ref value);
- return (_Id = value);
+ return _Id = value;
}
+
protected set
{
int oldValue = _Id;
@@ -125,7 +154,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Name
+ /// Backing field for Name.
/// </summary>
protected string _Name;
/// <summary>
@@ -149,8 +178,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Name;
GetName(ref value);
- return (_Name = value);
+ return _Name = value;
}
+
set
{
string oldValue = _Name;
@@ -163,7 +193,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Required, ConcurrenyToken
+ /// Required, ConcurrenyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
@@ -182,7 +212,6 @@ namespace Jellyfin.Data.Entities
[ForeignKey("Chapter_Chapters_Id")]
public virtual ICollection<Chapter> Chapters { get; protected set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/Season.cs b/Jellyfin.Data/Entities/Season.cs
index 96e89cde0..4b1e78575 100644
--- a/Jellyfin.Data/Entities/Season.cs
+++ b/Jellyfin.Data/Entities/Season.cs
@@ -31,7 +31,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
/// <param name="_series0"></param>
@@ -42,7 +42,11 @@ namespace Jellyfin.Data.Entities
this.UrlId = urlid;
- if (_series0 == null) throw new ArgumentNullException(nameof(_series0));
+ if (_series0 == null)
+ {
+ throw new ArgumentNullException(nameof(_series0));
+ }
+
_series0.Seasons.Add(this);
this.SeasonMetadata = new HashSet<SeasonMetadata>();
@@ -66,7 +70,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for SeasonNumber
+ /// Backing field for SeasonNumber.
/// </summary>
protected int? _SeasonNumber;
/// <summary>
@@ -84,8 +88,9 @@ namespace Jellyfin.Data.Entities
{
int? value = _SeasonNumber;
GetSeasonNumber(ref value);
- return (_SeasonNumber = value);
+ return _SeasonNumber = value;
}
+
set
{
int? oldValue = _SeasonNumber;
@@ -105,7 +110,6 @@ namespace Jellyfin.Data.Entities
[ForeignKey("Episode_Episodes_Id")]
public virtual ICollection<Episode> Episodes { get; protected set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/SeasonMetadata.cs b/Jellyfin.Data/Entities/SeasonMetadata.cs
index 64ecbfbfa..10d19875e 100644
--- a/Jellyfin.Data/Entities/SeasonMetadata.cs
+++ b/Jellyfin.Data/Entities/SeasonMetadata.cs
@@ -25,22 +25,33 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
- /// <param name="title">The title or name of the object</param>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="title">The title or name of the object.</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="_season0"></param>
public SeasonMetadata(string title, string language, DateTime dateadded, DateTime datemodified, Season _season0)
{
- if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ if (string.IsNullOrEmpty(title))
+ {
+ throw new ArgumentNullException(nameof(title));
+ }
+
this.Title = title;
- if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ if (string.IsNullOrEmpty(language))
+ {
+ throw new ArgumentNullException(nameof(language));
+ }
+
this.Language = language;
- if (_season0 == null) throw new ArgumentNullException(nameof(_season0));
- _season0.SeasonMetadata.Add(this);
+ if (_season0 == null)
+ {
+ throw new ArgumentNullException(nameof(_season0));
+ }
+ _season0.SeasonMetadata.Add(this);
Init();
}
@@ -48,8 +59,8 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Static create function (for use in LINQ queries, etc.)
/// </summary>
- /// <param name="title">The title or name of the object</param>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="title">The title or name of the object.</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="_season0"></param>
public static SeasonMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, Season _season0)
{
@@ -61,7 +72,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Outline
+ /// Backing field for Outline.
/// </summary>
protected string _Outline;
/// <summary>
@@ -84,8 +95,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Outline;
GetOutline(ref value);
- return (_Outline = value);
+ return _Outline = value;
}
+
set
{
string oldValue = _Outline;
@@ -100,7 +112,6 @@ namespace Jellyfin.Data.Entities
/*************************************************************************
* Navigation properties
*************************************************************************/
-
}
}
diff --git a/Jellyfin.Data/Entities/Series.cs b/Jellyfin.Data/Entities/Series.cs
index 4f25c38b7..bede14acf 100644
--- a/Jellyfin.Data/Entities/Series.cs
+++ b/Jellyfin.Data/Entities/Series.cs
@@ -20,7 +20,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
public Series(Guid urlid, DateTime dateadded)
@@ -47,7 +47,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for AirsDayOfWeek
+ /// Backing field for AirsDayOfWeek.
/// </summary>
protected DayOfWeek? _AirsDayOfWeek;
/// <summary>
@@ -65,8 +65,9 @@ namespace Jellyfin.Data.Entities
{
DayOfWeek? value = _AirsDayOfWeek;
GetAirsDayOfWeek(ref value);
- return (_AirsDayOfWeek = value);
+ return _AirsDayOfWeek = value;
}
+
set
{
DayOfWeek? oldValue = _AirsDayOfWeek;
@@ -79,7 +80,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for AirsTime
+ /// Backing field for AirsTime.
/// </summary>
protected DateTimeOffset? _AirsTime;
/// <summary>
@@ -92,7 +93,7 @@ namespace Jellyfin.Data.Entities
partial void GetAirsTime(ref DateTimeOffset? result);
/// <summary>
- /// The time the show airs, ignore the date portion
+ /// The time the show airs, ignore the date portion.
/// </summary>
public DateTimeOffset? AirsTime
{
@@ -100,8 +101,9 @@ namespace Jellyfin.Data.Entities
{
DateTimeOffset? value = _AirsTime;
GetAirsTime(ref value);
- return (_AirsTime = value);
+ return _AirsTime = value;
}
+
set
{
DateTimeOffset? oldValue = _AirsTime;
@@ -114,7 +116,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for FirstAired
+ /// Backing field for FirstAired.
/// </summary>
protected DateTimeOffset? _FirstAired;
/// <summary>
@@ -132,8 +134,9 @@ namespace Jellyfin.Data.Entities
{
DateTimeOffset? value = _FirstAired;
GetFirstAired(ref value);
- return (_FirstAired = value);
+ return _FirstAired = value;
}
+
set
{
DateTimeOffset? oldValue = _FirstAired;
@@ -153,7 +156,6 @@ namespace Jellyfin.Data.Entities
[ForeignKey("Season_Seasons_Id")]
public virtual ICollection<Season> Seasons { get; protected set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/SeriesMetadata.cs b/Jellyfin.Data/Entities/SeriesMetadata.cs
index 52691783f..16eb59315 100644
--- a/Jellyfin.Data/Entities/SeriesMetadata.cs
+++ b/Jellyfin.Data/Entities/SeriesMetadata.cs
@@ -28,20 +28,32 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
- /// <param name="title">The title or name of the object</param>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="title">The title or name of the object.</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="_series0"></param>
public SeriesMetadata(string title, string language, DateTime dateadded, DateTime datemodified, Series _series0)
{
- if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ if (string.IsNullOrEmpty(title))
+ {
+ throw new ArgumentNullException(nameof(title));
+ }
+
this.Title = title;
- if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ if (string.IsNullOrEmpty(language))
+ {
+ throw new ArgumentNullException(nameof(language));
+ }
+
this.Language = language;
- if (_series0 == null) throw new ArgumentNullException(nameof(_series0));
+ if (_series0 == null)
+ {
+ throw new ArgumentNullException(nameof(_series0));
+ }
+
_series0.SeriesMetadata.Add(this);
this.Networks = new HashSet<Company>();
@@ -52,8 +64,8 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Static create function (for use in LINQ queries, etc.)
/// </summary>
- /// <param name="title">The title or name of the object</param>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="title">The title or name of the object.</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="_series0"></param>
public static SeriesMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, Series _series0)
{
@@ -65,7 +77,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for Outline
+ /// Backing field for Outline.
/// </summary>
protected string _Outline;
/// <summary>
@@ -88,8 +100,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Outline;
GetOutline(ref value);
- return (_Outline = value);
+ return _Outline = value;
}
+
set
{
string oldValue = _Outline;
@@ -102,7 +115,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Plot
+ /// Backing field for Plot.
/// </summary>
protected string _Plot;
/// <summary>
@@ -125,8 +138,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Plot;
GetPlot(ref value);
- return (_Plot = value);
+ return _Plot = value;
}
+
set
{
string oldValue = _Plot;
@@ -139,7 +153,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Tagline
+ /// Backing field for Tagline.
/// </summary>
protected string _Tagline;
/// <summary>
@@ -162,8 +176,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Tagline;
GetTagline(ref value);
- return (_Tagline = value);
+ return _Tagline = value;
}
+
set
{
string oldValue = _Tagline;
@@ -176,7 +191,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Backing field for Country
+ /// Backing field for Country.
/// </summary>
protected string _Country;
/// <summary>
@@ -199,8 +214,9 @@ namespace Jellyfin.Data.Entities
{
string value = _Country;
GetCountry(ref value);
- return (_Country = value);
+ return _Country = value;
}
+
set
{
string oldValue = _Country;
@@ -217,7 +233,6 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
[ForeignKey("Company_Networks_Id")]
public virtual ICollection<Company> Networks { get; protected set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/Track.cs b/Jellyfin.Data/Entities/Track.cs
index 079d73d2b..b7d7b5873 100644
--- a/Jellyfin.Data/Entities/Track.cs
+++ b/Jellyfin.Data/Entities/Track.cs
@@ -31,7 +31,7 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
/// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
/// <param name="_musicalbum0"></param>
@@ -42,7 +42,11 @@ namespace Jellyfin.Data.Entities
this.UrlId = urlid;
- if (_musicalbum0 == null) throw new ArgumentNullException(nameof(_musicalbum0));
+ if (_musicalbum0 == null)
+ {
+ throw new ArgumentNullException(nameof(_musicalbum0));
+ }
+
_musicalbum0.Tracks.Add(this);
this.Releases = new HashSet<Release>();
@@ -66,7 +70,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Backing field for TrackNumber
+ /// Backing field for TrackNumber.
/// </summary>
protected int? _TrackNumber;
/// <summary>
@@ -84,8 +88,9 @@ namespace Jellyfin.Data.Entities
{
int? value = _TrackNumber;
GetTrackNumber(ref value);
- return (_TrackNumber = value);
+ return _TrackNumber = value;
}
+
set
{
int? oldValue = _TrackNumber;
@@ -106,7 +111,6 @@ namespace Jellyfin.Data.Entities
[ForeignKey("TrackMetadata_TrackMetadata_Id")]
public virtual ICollection<TrackMetadata> TrackMetadata { get; protected set; }
-
}
}
diff --git a/Jellyfin.Data/Entities/TrackMetadata.cs b/Jellyfin.Data/Entities/TrackMetadata.cs
index 86c9161f6..23e1219aa 100644
--- a/Jellyfin.Data/Entities/TrackMetadata.cs
+++ b/Jellyfin.Data/Entities/TrackMetadata.cs
@@ -24,22 +24,33 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Public constructor with required data
+ /// Public constructor with required data.
/// </summary>
- /// <param name="title">The title or name of the object</param>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="title">The title or name of the object.</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="_track0"></param>
public TrackMetadata(string title, string language, DateTime dateadded, DateTime datemodified, Track _track0)
{
- if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ if (string.IsNullOrEmpty(title))
+ {
+ throw new ArgumentNullException(nameof(title));
+ }
+
this.Title = title;
- if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ if (string.IsNullOrEmpty(language))
+ {
+ throw new ArgumentNullException(nameof(language));
+ }
+
this.Language = language;
- if (_track0 == null) throw new ArgumentNullException(nameof(_track0));
- _track0.TrackMetadata.Add(this);
+ if (_track0 == null)
+ {
+ throw new ArgumentNullException(nameof(_track0));
+ }
+ _track0.TrackMetadata.Add(this);
Init();
}
@@ -47,8 +58,8 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Static create function (for use in LINQ queries, etc.)
/// </summary>
- /// <param name="title">The title or name of the object</param>
- /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="title">The title or name of the object.</param>
+ /// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="_track0"></param>
public static TrackMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, Track _track0)
{
@@ -62,7 +73,6 @@ namespace Jellyfin.Data.Entities
/*************************************************************************
* Navigation properties
*************************************************************************/
-
}
}
diff --git a/Jellyfin.Data/Enums/PreferenceKind.cs b/Jellyfin.Data/Enums/PreferenceKind.cs
index de8eecc73..a54d789af 100644
--- a/Jellyfin.Data/Enums/PreferenceKind.cs
+++ b/Jellyfin.Data/Enums/PreferenceKind.cs
@@ -26,7 +26,7 @@ namespace Jellyfin.Data.Enums
EnabledDevices = 3,
/// <summary>
- /// A list of enabled channels
+ /// A list of enabled channels.
/// </summary>
EnabledChannels = 4,
diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj
index 282ea511c..8ce0f3848 100644
--- a/Jellyfin.Data/Jellyfin.Data.csproj
+++ b/Jellyfin.Data/Jellyfin.Data.csproj
@@ -19,9 +19,8 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.5" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.5" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="3.1.5" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.6" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.6" />
</ItemGroup>
</Project>
diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
index dcac1b34b..21748ca19 100644
--- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
+++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
@@ -24,11 +24,11 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.5">
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.5">
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs
index f574ebc66..53120a763 100644
--- a/Jellyfin.Server.Implementations/JellyfinDb.cs
+++ b/Jellyfin.Server.Implementations/JellyfinDb.cs
@@ -34,50 +34,89 @@ namespace Jellyfin.Server.Implementations
public virtual DbSet<Preference> Preferences { get; set; }
public virtual DbSet<User> Users { get; set; }
+
/*public virtual DbSet<Artwork> Artwork { get; set; }
+
public virtual DbSet<Book> Books { get; set; }
+
public virtual DbSet<BookMetadata> BookMetadata { get; set; }
+
public virtual DbSet<Chapter> Chapters { get; set; }
+
public virtual DbSet<Collection> Collections { get; set; }
+
public virtual DbSet<CollectionItem> CollectionItems { get; set; }
+
public virtual DbSet<Company> Companies { get; set; }
+
public virtual DbSet<CompanyMetadata> CompanyMetadata { get; set; }
+
public virtual DbSet<CustomItem> CustomItems { get; set; }
+
public virtual DbSet<CustomItemMetadata> CustomItemMetadata { get; set; }
+
public virtual DbSet<Episode> Episodes { get; set; }
+
public virtual DbSet<EpisodeMetadata> EpisodeMetadata { get; set; }
+
public virtual DbSet<Genre> Genres { get; set; }
+
public virtual DbSet<Group> Groups { get; set; }
+
public virtual DbSet<Library> Libraries { get; set; }
+
public virtual DbSet<LibraryItem> LibraryItems { get; set; }
+
public virtual DbSet<LibraryRoot> LibraryRoot { get; set; }
+
public virtual DbSet<MediaFile> MediaFiles { get; set; }
+
public virtual DbSet<MediaFileStream> MediaFileStream { get; set; }
+
public virtual DbSet<Metadata> Metadata { get; set; }
+
public virtual DbSet<MetadataProvider> MetadataProviders { get; set; }
+
public virtual DbSet<MetadataProviderId> MetadataProviderIds { get; set; }
+
public virtual DbSet<Movie> Movies { get; set; }
+
public virtual DbSet<MovieMetadata> MovieMetadata { get; set; }
+
public virtual DbSet<MusicAlbum> MusicAlbums { get; set; }
+
public virtual DbSet<MusicAlbumMetadata> MusicAlbumMetadata { get; set; }
+
public virtual DbSet<Person> People { get; set; }
+
public virtual DbSet<PersonRole> PersonRoles { get; set; }
+
public virtual DbSet<Photo> Photo { get; set; }
+
public virtual DbSet<PhotoMetadata> PhotoMetadata { get; set; }
+
public virtual DbSet<ProviderMapping> ProviderMappings { get; set; }
+
public virtual DbSet<Rating> Ratings { get; set; }
/// <summary>
/// Repository for global::Jellyfin.Data.Entities.RatingSource - This is the entity to
- /// store review ratings, not age ratings
+ /// store review ratings, not age ratings.
/// </summary>
public virtual DbSet<RatingSource> RatingSources { get; set; }
+
public virtual DbSet<Release> Releases { get; set; }
+
public virtual DbSet<Season> Seasons { get; set; }
+
public virtual DbSet<SeasonMetadata> SeasonMetadata { get; set; }
+
public virtual DbSet<Series> Series { get; set; }
+
public virtual DbSet<SeriesMetadata> SeriesMetadata { get; set; }
+
public virtual DbSet<Track> Tracks { get; set; }
+
public virtual DbSet<TrackMetadata> TrackMetadata { get; set; }*/
/// <inheritdoc/>
diff --git a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs
index 162dc6f5e..f79e433a6 100644
--- a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs
+++ b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs
@@ -5,7 +5,6 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
-using MediaBrowser.Common;
using MediaBrowser.Common.Cryptography;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Model.Cryptography;
@@ -63,25 +62,29 @@ namespace Jellyfin.Server.Implementations.Users
});
}
- byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
-
- PasswordHash readyHash = PasswordHash.Parse(resolvedUser.Password);
- if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)
- || _cryptographyProvider.DefaultHashMethod == readyHash.Id)
+ // Handle the case when the stored password is null, but the user tried to login with a password
+ if (resolvedUser.Password != null)
{
- byte[] calculatedHash = _cryptographyProvider.ComputeHash(
- readyHash.Id,
- passwordBytes,
- readyHash.Salt.ToArray());
+ byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
- if (readyHash.Hash.SequenceEqual(calculatedHash))
+ PasswordHash readyHash = PasswordHash.Parse(resolvedUser.Password);
+ if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)
+ || _cryptographyProvider.DefaultHashMethod == readyHash.Id)
{
- success = true;
+ byte[] calculatedHash = _cryptographyProvider.ComputeHash(
+ readyHash.Id,
+ passwordBytes,
+ readyHash.Salt.ToArray());
+
+ if (readyHash.Hash.SequenceEqual(calculatedHash))
+ {
+ success = true;
+ }
+ }
+ else
+ {
+ throw new AuthenticationException($"Requested crypto method not available in provider: {readyHash.Id}");
}
- }
- else
- {
- throw new AuthenticationException($"Requested crypto method not available in provider: {readyHash.Id}");
}
if (!success)
@@ -113,29 +116,5 @@ namespace Jellyfin.Server.Implementations.Users
return Task.CompletedTask;
}
-
- /// <inheritdoc />
- public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
- {
- if (newPassword != null)
- {
- newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword).ToString();
- }
-
- if (string.IsNullOrWhiteSpace(newPasswordHash))
- {
- throw new ArgumentNullException(nameof(newPasswordHash));
- }
-
- user.EasyPassword = newPasswordHash;
- }
-
- /// <inheritdoc />
- public string? GetEasyPasswordHash(User user)
- {
- return string.IsNullOrEmpty(user.EasyPassword)
- ? null
- : Hex.Encode(PasswordHash.Parse(user.EasyPassword).Hash);
- }
}
}
diff --git a/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs b/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs
index 491aba1d4..e38cd07f0 100644
--- a/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs
+++ b/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs
@@ -34,17 +34,5 @@ namespace Jellyfin.Server.Implementations.Users
{
return Task.CompletedTask;
}
-
- /// <inheritdoc />
- public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
- {
- // Nothing here
- }
-
- /// <inheritdoc />
- public string GetEasyPasswordHash(User user)
- {
- return string.Empty;
- }
}
}
diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs
index 904114758..343f452c7 100644
--- a/Jellyfin.Server.Implementations/Users/UserManager.cs
+++ b/Jellyfin.Server.Implementations/Users/UserManager.cs
@@ -12,6 +12,7 @@ using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common;
using MediaBrowser.Common.Cryptography;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Drawing;
@@ -22,6 +23,7 @@ using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Users;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Server.Implementations.Users
@@ -85,7 +87,19 @@ namespace Jellyfin.Server.Implementations.Users
public event EventHandler<GenericEventArgs<User>>? OnUserLockedOut;
/// <inheritdoc/>
- public IEnumerable<User> Users => _dbProvider.CreateContext().Users;
+ public IEnumerable<User> Users
+ {
+ get
+ {
+ using var dbContext = _dbProvider.CreateContext();
+ return dbContext.Users
+ .Include(user => user.Permissions)
+ .Include(user => user.Preferences)
+ .Include(user => user.AccessSchedules)
+ .Include(user => user.ProfileImage)
+ .ToList();
+ }
+ }
/// <inheritdoc/>
public IEnumerable<Guid> UsersIds => _dbProvider.CreateContext().Users.Select(u => u.Id);
@@ -98,7 +112,13 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("Guid can't be empty", nameof(id));
}
- return _dbProvider.CreateContext().Users.Find(id);
+ using var dbContext = _dbProvider.CreateContext();
+ return dbContext.Users
+ .Include(user => user.Permissions)
+ .Include(user => user.Preferences)
+ .Include(user => user.AccessSchedules)
+ .Include(user => user.ProfileImage)
+ .FirstOrDefault(user => user.Id == id);
}
/// <inheritdoc/>
@@ -109,9 +129,14 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("Invalid username", nameof(name));
}
- // This can't use an overload with StringComparer because that would cause the query to
- // have to be evaluated client-side.
- return _dbProvider.CreateContext().Users.FirstOrDefault(u => string.Equals(u.Username, name));
+ using var dbContext = _dbProvider.CreateContext();
+ return dbContext.Users
+ .Include(user => user.Permissions)
+ .Include(user => user.Preferences)
+ .Include(user => user.AccessSchedules)
+ .Include(user => user.ProfileImage)
+ .AsEnumerable()
+ .FirstOrDefault(u => string.Equals(u.Username, name, StringComparison.OrdinalIgnoreCase));
}
/// <inheritdoc/>
@@ -132,7 +157,7 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("The new and old names must be different.");
}
- if (Users.Any(u => u.Id != user.Id && u.Username.Equals(newName, StringComparison.OrdinalIgnoreCase)))
+ if (Users.Any(u => u.Id != user.Id && u.Username.Equals(newName, StringComparison.Ordinal)))
{
throw new ArgumentException(string.Format(
CultureInfo.InvariantCulture,
@@ -149,7 +174,7 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public void UpdateUser(User user)
{
- var dbContext = _dbProvider.CreateContext();
+ using var dbContext = _dbProvider.CreateContext();
dbContext.Users.Update(user);
dbContext.SaveChanges();
}
@@ -157,7 +182,7 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public async Task UpdateUserAsync(User user)
{
- var dbContext = _dbProvider.CreateContext();
+ await using var dbContext = _dbProvider.CreateContext();
dbContext.Users.Update(user);
await dbContext.SaveChangesAsync().ConfigureAwait(false);
@@ -171,7 +196,7 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)");
}
- var dbContext = _dbProvider.CreateContext();
+ using var dbContext = _dbProvider.CreateContext();
// TODO: Remove after user item data is migrated.
var max = dbContext.Users.Any() ? dbContext.Users.Select(u => u.InternalId).Max() : 0;
@@ -192,15 +217,20 @@ namespace Jellyfin.Server.Implementations.Users
}
/// <inheritdoc/>
- public void DeleteUser(User user)
+ public void DeleteUser(Guid userId)
{
+ using var dbContext = _dbProvider.CreateContext();
+ var user = dbContext.Users
+ .Include(u => u.Permissions)
+ .Include(u => u.Preferences)
+ .Include(u => u.AccessSchedules)
+ .Include(u => u.ProfileImage)
+ .FirstOrDefault(u => u.Id == userId);
if (user == null)
{
- throw new ArgumentNullException(nameof(user));
+ throw new ResourceNotFoundException(nameof(userId));
}
- var dbContext = _dbProvider.CreateContext();
-
if (dbContext.Users.Find(user.Id) == null)
{
throw new ArgumentException(string.Format(
@@ -226,9 +256,18 @@ namespace Jellyfin.Server.Implementations.Users
CultureInfo.InvariantCulture,
"The user '{0}' cannot be deleted because there must be at least one admin user in the system.",
user.Username),
- nameof(user));
+ nameof(userId));
+ }
+
+ // Clear all entities related to the user from the database.
+ if (user.ProfileImage != null)
+ {
+ dbContext.Remove(user.ProfileImage);
}
+ dbContext.RemoveRange(user.Permissions);
+ dbContext.RemoveRange(user.Preferences);
+ dbContext.RemoveRange(user.AccessSchedules);
dbContext.Users.Remove(user);
dbContext.SaveChanges();
OnUserDeleted?.Invoke(this, new GenericEventArgs<User>(user));
@@ -263,7 +302,17 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public void ChangeEasyPassword(User user, string newPassword, string? newPasswordSha1)
{
- GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordSha1);
+ if (newPassword != null)
+ {
+ newPasswordSha1 = _cryptoProvider.CreatePasswordHash(newPassword).ToString();
+ }
+
+ if (string.IsNullOrWhiteSpace(newPasswordSha1))
+ {
+ throw new ArgumentNullException(nameof(newPasswordSha1));
+ }
+
+ user.EasyPassword = newPasswordSha1;
UpdateUser(user);
OnUserPasswordChanged?.Invoke(this, new GenericEventArgs<User>(user));
@@ -361,7 +410,7 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentNullException(nameof(username));
}
- var user = Users.ToList().FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
+ var user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
bool success;
IAuthenticationProvider? authenticationProvider;
@@ -389,8 +438,7 @@ namespace Jellyfin.Server.Implementations.Users
// Search the database for the user again
// the authentication provider might have created it
- user = Users
- .ToList().FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
+ user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy)
{
@@ -527,7 +575,7 @@ namespace Jellyfin.Server.Implementations.Users
public void Initialize()
{
// TODO: Refactor the startup wizard so that it doesn't require a user to already exist.
- var dbContext = _dbProvider.CreateContext();
+ using var dbContext = _dbProvider.CreateContext();
if (dbContext.Users.Any())
{
@@ -590,7 +638,14 @@ namespace Jellyfin.Server.Implementations.Users
public void UpdateConfiguration(Guid userId, UserConfiguration config)
{
var dbContext = _dbProvider.CreateContext();
- var user = dbContext.Users.Find(userId) ?? throw new ArgumentException("No user exists with given Id!");
+ var user = dbContext.Users
+ .Include(u => u.Permissions)
+ .Include(u => u.Preferences)
+ .Include(u => u.AccessSchedules)
+ .Include(u => u.ProfileImage)
+ .FirstOrDefault(u => u.Id == userId)
+ ?? throw new ArgumentException("No user exists with given Id!");
+
user.SubtitleMode = config.SubtitleMode;
user.HidePlayedInLatest = config.HidePlayedInLatest;
user.EnableLocalPassword = config.EnableLocalPassword;
@@ -616,7 +671,13 @@ namespace Jellyfin.Server.Implementations.Users
public void UpdatePolicy(Guid userId, UserPolicy policy)
{
var dbContext = _dbProvider.CreateContext();
- var user = dbContext.Users.Find(userId) ?? throw new ArgumentException("No user exists with given Id!");
+ var user = dbContext.Users
+ .Include(u => u.Permissions)
+ .Include(u => u.Preferences)
+ .Include(u => u.AccessSchedules)
+ .Include(u => u.ProfileImage)
+ .FirstOrDefault(u => u.Id == 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
@@ -679,7 +740,7 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public void ClearProfileImage(User user)
{
- var dbContext = _dbProvider.CreateContext();
+ using var dbContext = _dbProvider.CreateContext();
dbContext.Remove(user.ProfileImage);
dbContext.SaveChanges();
}
diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs
index fe07411a6..207eaa98d 100644
--- a/Jellyfin.Server/CoreAppHost.cs
+++ b/Jellyfin.Server/CoreAppHost.cs
@@ -66,8 +66,7 @@ namespace Jellyfin.Server
// TODO: Set up scoping and use AddDbContextPool
serviceCollection.AddDbContext<JellyfinDb>(
options => options
- .UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}")
- .UseLazyLoadingProxies(),
+ .UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"),
ServiceLifetime.Transient);
serviceCollection.AddSingleton<JellyfinDbProvider>();
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index e4ecd343c..cfbabf795 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -195,7 +195,7 @@ namespace Jellyfin.Server.Extensions
// Order actions by route path, then by http method.
c.OrderActionsBy(description =>
- $"{description.ActionDescriptor.RouteValues["controller"]}_{description.HttpMethod}");
+ $"{description.ActionDescriptor.RouteValues["controller"]}_{description.RelativePath}");
// Use method name as operationId
c.CustomOperationIds(description =>
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index 7457830fa..b1bd38cff 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -40,18 +40,18 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="CommandLineParser" Version="2.7.82" />
- <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.5" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.5" />
- <PackageReference Include="prometheus-net" Version="3.5.0" />
- <PackageReference Include="prometheus-net.AspNetCore" Version="3.5.0" />
+ <PackageReference Include="CommandLineParser" Version="2.8.0" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.6" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.6" />
+ <PackageReference Include="prometheus-net" Version="3.6.0" />
+ <PackageReference Include="prometheus-net.AspNetCore" Version="3.6.0" />
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
- <PackageReference Include="Serilog.Sinks.Graylog" Version="2.1.2" />
+ <PackageReference Include="Serilog.Sinks.Graylog" Version="2.1.3" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.3" />
<PackageReference Include="SQLitePCLRaw.provider.sqlite3.netstandard11" Version="1.1.14" />
</ItemGroup>
diff --git a/Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs b/Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs
new file mode 100644
index 000000000..3122d92cb
--- /dev/null
+++ b/Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs
@@ -0,0 +1,78 @@
+using System.Diagnostics;
+using System.Globalization;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Configuration;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Extensions;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Server.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;
+
+ private readonly bool _enableWarning;
+ private readonly long _warningThreshold;
+
+ /// <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>
+ /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
+ public ResponseTimeMiddleware(
+ RequestDelegate next,
+ ILogger<ResponseTimeMiddleware> logger,
+ IServerConfigurationManager serverConfigurationManager)
+ {
+ _next = next;
+ _logger = logger;
+
+ _enableWarning = serverConfigurationManager.Configuration.EnableSlowResponseWarning;
+ _warningThreshold = serverConfigurationManager.Configuration.SlowResponseThresholdMs;
+ }
+
+ /// <summary>
+ /// Invoke request.
+ /// </summary>
+ /// <param name="context">Request context.</param>
+ /// <returns>Task.</returns>
+ public async Task Invoke(HttpContext context)
+ {
+ var watch = new Stopwatch();
+ watch.Start();
+
+ context.Response.OnStarting(() =>
+ {
+ watch.Stop();
+ LogWarning(context, watch);
+ var responseTimeForCompleteRequest = watch.ElapsedMilliseconds;
+ context.Response.Headers[ResponseHeaderResponseTime] = responseTimeForCompleteRequest.ToString(CultureInfo.InvariantCulture);
+ return Task.CompletedTask;
+ });
+
+ // Call the next delegate/middleware in the pipeline
+ await this._next(context).ConfigureAwait(false);
+ }
+
+ private void LogWarning(HttpContext context, Stopwatch watch)
+ {
+ if (_enableWarning && watch.ElapsedMilliseconds > _warningThreshold)
+ {
+ _logger.LogWarning(
+ "Slow HTTP Response from {url} to {remoteIp} in {elapsed:g} with Status Code {statusCode}",
+ context.Request.GetDisplayUrl(),
+ context.Connection.RemoteIpAddress,
+ watch.Elapsed,
+ context.Response.StatusCode);
+ }
+ }
+ }
+}
diff --git a/Jellyfin.Server/Migrations/IMigrationRoutine.cs b/Jellyfin.Server/Migrations/IMigrationRoutine.cs
index 6b5780a26..c1000eede 100644
--- a/Jellyfin.Server/Migrations/IMigrationRoutine.cs
+++ b/Jellyfin.Server/Migrations/IMigrationRoutine.cs
@@ -18,6 +18,11 @@ namespace Jellyfin.Server.Migrations
public string Name { get; }
/// <summary>
+ /// Gets a value indicating whether to perform migration on a new install.
+ /// </summary>
+ public bool PerformOnNewInstall { get; }
+
+ /// <summary>
/// Execute the migration routine.
/// </summary>
public void Perform();
diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs
index c98263223..fe8910c3c 100644
--- a/Jellyfin.Server/Migrations/MigrationRunner.cs
+++ b/Jellyfin.Server/Migrations/MigrationRunner.cs
@@ -20,6 +20,7 @@ namespace Jellyfin.Server.Migrations
typeof(Routines.CreateUserLoggingConfigFile),
typeof(Routines.MigrateActivityLogDb),
typeof(Routines.RemoveDuplicateExtras),
+ typeof(Routines.AddDefaultPluginRepository),
typeof(Routines.MigrateUserDb)
};
@@ -42,9 +43,8 @@ namespace Jellyfin.Server.Migrations
// If startup wizard is not finished, this is a fresh install.
// Don't run any migrations, just mark all of them as applied.
logger.LogInformation("Marking all known migrations as applied because this is a fresh install");
- migrationOptions.Applied.AddRange(migrations.Select(m => (m.Id, m.Name)));
+ migrationOptions.Applied.AddRange(migrations.Where(m => !m.PerformOnNewInstall).Select(m => (m.Id, m.Name)));
host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions);
- return;
}
var appliedMigrationIds = migrationOptions.Applied.Select(m => m.Id).ToHashSet();
diff --git a/Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs b/Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs
new file mode 100644
index 000000000..f6d8c9cc0
--- /dev/null
+++ b/Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs
@@ -0,0 +1,45 @@
+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>
+ public class AddDefaultPluginRepository : IMigrationRoutine
+ {
+ 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 Guid Id => Guid.Parse("EB58EBEE-9514-4B9B-8225-12E1A40020DF");
+
+ /// <inheritdoc/>
+ public string Name => "AddDefaultPluginRepository";
+
+ /// <inheritdoc/>
+ public bool PerformOnNewInstall => true;
+
+ /// <inheritdoc/>
+ public void Perform()
+ {
+ _serverConfigurationManager.Configuration.PluginRepositories.Add(_defaultRepositoryInfo);
+ _serverConfigurationManager.SaveConfiguration();
+ }
+ }
+}
diff --git a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs
index b15e09290..6821630db 100644
--- a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs
+++ b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs
@@ -49,6 +49,9 @@ namespace Jellyfin.Server.Migrations.Routines
public string Name => "CreateLoggingConfigHeirarchy";
/// <inheritdoc/>
+ public bool PerformOnNewInstall => false;
+
+ /// <inheritdoc/>
public void Perform()
{
var logDirectory = _appPaths.ConfigurationDirectoryPath;
diff --git a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs
index c18aa1629..0925a87b5 100644
--- a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs
+++ b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs
@@ -26,6 +26,9 @@ namespace Jellyfin.Server.Migrations.Routines
public string Name => "DisableTranscodingThrottling";
/// <inheritdoc/>
+ public bool PerformOnNewInstall => false;
+
+ /// <inheritdoc/>
public void Perform()
{
// Set EnableThrottling to false since it wasn't used before and may introduce issues
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
index fb3466e13..6048160c6 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
@@ -42,6 +42,9 @@ namespace Jellyfin.Server.Migrations.Routines
public string Name => "MigrateActivityLogDatabase";
/// <inheritdoc/>
+ public bool PerformOnNewInstall => false;
+
+ /// <inheritdoc/>
public void Perform()
{
var logLevelDictionary = new Dictionary<string, LogLevel>(StringComparer.OrdinalIgnoreCase)
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
index 2be10c708..274e6ab73 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
@@ -55,6 +55,9 @@ namespace Jellyfin.Server.Migrations.Routines
public string Name => "MigrateUserDatabase";
/// <inheritdoc/>
+ public bool PerformOnNewInstall => false;
+
+ /// <inheritdoc/>
public void Perform()
{
var dataPath = _paths.DataPath;
diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs b/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs
index 2ebef0241..6c26e47e1 100644
--- a/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs
+++ b/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs
@@ -30,6 +30,9 @@ namespace Jellyfin.Server.Migrations.Routines
public string Name => "RemoveDuplicateExtras";
/// <inheritdoc/>
+ public bool PerformOnNewInstall => false;
+
+ /// <inheritdoc/>
public void Perform()
{
var dataPath = _paths.DataPath;
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index dfc7bbbb1..288a5c259 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
@@ -6,7 +7,6 @@ using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
-using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using CommandLine;
@@ -59,20 +59,15 @@ namespace Jellyfin.Server
/// <returns><see cref="Task" />.</returns>
public static Task Main(string[] args)
{
- // For backwards compatibility.
- // Modify any input arguments now which start with single-hyphen to POSIX standard
- // double-hyphen to allow parsing by CommandLineParser package.
- const string Pattern = @"^(-[^-\s]{2})"; // Match -xx, not -x, not --xx, not xx
- const string Substitution = @"-$1"; // Prepend with additional single-hyphen
- var regex = new Regex(Pattern);
- for (var i = 0; i < args.Length; i++)
+ static Task ErrorParsingArguments(IEnumerable<Error> errors)
{
- args[i] = regex.Replace(args[i], Substitution);
+ 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, _ => Task.CompletedTask);
+ .MapResult(StartApp, ErrorParsingArguments);
}
/// <summary>
@@ -279,10 +274,10 @@ namespace Jellyfin.Server
var addresses = appHost.ServerConfigurationManager
.Configuration
.LocalNetworkAddresses
- .Select(appHost.NormalizeConfiguredLocalAddress)
+ .Select(x => appHost.NormalizeConfiguredLocalAddress(x))
.Where(i => i != null)
.ToHashSet();
- if (addresses.Any() && !addresses.Contains(IPAddress.Any))
+ if (addresses.Count > 0 && !addresses.Contains(IPAddress.Any))
{
if (!addresses.Contains(IPAddress.Loopback))
{
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index a7bc15614..edf023fa2 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -63,6 +63,8 @@ namespace Jellyfin.Server
app.UseMiddleware<ExceptionMiddleware>();
+ app.UseMiddleware<ResponseTimeMiddleware>();
+
app.UseWebSockets();
app.UseResponseCompression();
diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs
index cc250b06e..41a1430d2 100644
--- a/Jellyfin.Server/StartupOptions.cs
+++ b/Jellyfin.Server/StartupOptions.cs
@@ -80,10 +80,6 @@ namespace Jellyfin.Server
public string? RestartArgs { get; set; }
/// <inheritdoc />
- [Option("plugin-manifest-url", Required = false, HelpText = "A custom URL for the plugin repository JSON manifest")]
- public string? PluginManifestUrl { get; set; }
-
- /// <inheritdoc />
[Option("published-server-url", Required = false, HelpText = "Jellyfin Server URL to publish via auto discover process")]
public Uri? PublishedServerUrl { get; set; }
@@ -95,11 +91,6 @@ namespace Jellyfin.Server
{
var config = new Dictionary<string, string>();
- if (PluginManifestUrl != null)
- {
- config.Add(InstallationManager.PluginManifestUrlKey, PluginManifestUrl);
- }
-
if (NoWebClient)
{
config.Add(ConfigurationExtensions.HostWebClientKey, bool.FalseString);
@@ -110,6 +101,11 @@ namespace Jellyfin.Server
config.Add(UdpServer.AddressOverrideConfigKey, PublishedServerUrl.ToString());
}
+ if (FFmpegPath != null)
+ {
+ config.Add(ConfigurationExtensions.FfmpegPathKey, FFmpegPath);
+ }
+
return config;
}
}
diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs
index 9e651fb19..b041effb2 100644
--- a/MediaBrowser.Api/ApiEntryPoint.cs
+++ b/MediaBrowser.Api/ApiEntryPoint.cs
@@ -43,7 +43,7 @@ namespace MediaBrowser.Api
private readonly IMediaSourceManager _mediaSourceManager;
/// <summary>
- /// The active transcoding jobs
+ /// The active transcoding jobs.
/// </summary>
private readonly List<TranscodingJob> _activeTranscodingJobs = new List<TranscodingJob>();
@@ -293,7 +293,7 @@ namespace MediaBrowser.Api
/// <summary>
/// <summary>
- /// The progressive
+ /// The progressive.
/// </summary>
/// Called when [transcode failed to start].
/// </summary>
diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs
index 2ece16ee1..63a31a745 100644
--- a/MediaBrowser.Api/BaseApiService.cs
+++ b/MediaBrowser.Api/BaseApiService.cs
@@ -17,7 +17,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api
{
/// <summary>
- /// Class BaseApiService
+ /// Class BaseApiService.
/// </summary>
public abstract class BaseApiService : IService, IRequiresRequest
{
@@ -268,7 +268,6 @@ namespace MediaBrowser.Api
Name = name.Replace(BaseItem.SlugChar, '&'),
IncludeItemTypes = new[] { typeof(T).Name },
DtoOptions = dtoOptions
-
}).OfType<T>().FirstOrDefault();
result ??= libraryManager.GetItemList(new InternalItemsQuery
@@ -276,7 +275,6 @@ namespace MediaBrowser.Api
Name = name.Replace(BaseItem.SlugChar, '/'),
IncludeItemTypes = new[] { typeof(T).Name },
DtoOptions = dtoOptions
-
}).OfType<T>().FirstOrDefault();
result ??= libraryManager.GetItemList(new InternalItemsQuery
@@ -284,7 +282,6 @@ namespace MediaBrowser.Api
Name = name.Replace(BaseItem.SlugChar, '?'),
IncludeItemTypes = new[] { typeof(T).Name },
DtoOptions = dtoOptions
-
}).OfType<T>().FirstOrDefault();
return result;
diff --git a/MediaBrowser.Api/ChannelService.cs b/MediaBrowser.Api/ChannelService.cs
deleted file mode 100644
index fd9b8c396..000000000
--- a/MediaBrowser.Api/ChannelService.cs
+++ /dev/null
@@ -1,341 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Api.UserLibrary;
-using MediaBrowser.Controller.Channels;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Channels;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api
-{
- [Route("/Channels", "GET", Summary = "Gets available channels")]
- public class GetChannels : IReturn<QueryResult<BaseItemDto>>
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public Guid UserId { get; set; }
-
- /// <summary>
- /// Skips over a given number of items within the results. Use for paging.
- /// </summary>
- /// <value>The start index.</value>
- [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? StartIndex { get; set; }
-
- /// <summary>
- /// The maximum number of items to return
- /// </summary>
- /// <value>The limit.</value>
- [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? Limit { get; set; }
-
- [ApiMember(Name = "SupportsLatestItems", Description = "Optional. Filter by channels that support getting latest items.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? SupportsLatestItems { get; set; }
-
- public bool? SupportsMediaDeletion { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether this instance is favorite.
- /// </summary>
- /// <value><c>null</c> if [is favorite] contains no value, <c>true</c> if [is favorite]; otherwise, <c>false</c>.</value>
- public bool? IsFavorite { get; set; }
- }
-
- [Route("/Channels/{Id}/Features", "GET", Summary = "Gets features for a channel")]
- public class GetChannelFeatures : IReturn<ChannelFeatures>
- {
- [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
- }
-
- [Route("/Channels/Features", "GET", Summary = "Gets features for a channel")]
- public class GetAllChannelFeatures : IReturn<ChannelFeatures[]>
- {
- }
-
- [Route("/Channels/{Id}/Items", "GET", Summary = "Gets channel items")]
- public class GetChannelItems : IReturn<QueryResult<BaseItemDto>>, IHasItemFields
- {
- [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
-
- [ApiMember(Name = "FolderId", Description = "Folder Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string FolderId { get; set; }
-
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public Guid UserId { get; set; }
-
- /// <summary>
- /// Skips over a given number of items within the results. Use for paging.
- /// </summary>
- /// <value>The start index.</value>
- [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? StartIndex { get; set; }
-
- /// <summary>
- /// The maximum number of items to return
- /// </summary>
- /// <value>The limit.</value>
- [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? Limit { get; set; }
-
- [ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string SortOrder { get; set; }
-
- [ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string Filters { get; set; }
-
- [ApiMember(Name = "SortBy", Description = "Optional. Specify one or more sort orders, comma delimeted. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string SortBy { get; set; }
-
- [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string Fields { get; set; }
-
- /// <summary>
- /// Gets the filters.
- /// </summary>
- /// <returns>IEnumerable{ItemFilter}.</returns>
- public IEnumerable<ItemFilter> GetFilters()
- {
- var val = Filters;
-
- return string.IsNullOrEmpty(val)
- ? Array.Empty<ItemFilter>()
- : val.Split(',').Select(v => Enum.Parse<ItemFilter>(v, true));
- }
-
- /// <summary>
- /// Gets the order by.
- /// </summary>
- /// <returns>IEnumerable{ItemSortBy}.</returns>
- public ValueTuple<string, SortOrder>[] GetOrderBy()
- {
- return BaseItemsRequest.GetOrderBy(SortBy, SortOrder);
- }
- }
-
- [Route("/Channels/Items/Latest", "GET", Summary = "Gets channel items")]
- public class GetLatestChannelItems : IReturn<QueryResult<BaseItemDto>>, IHasItemFields
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public Guid UserId { get; set; }
-
- /// <summary>
- /// Skips over a given number of items within the results. Use for paging.
- /// </summary>
- /// <value>The start index.</value>
- [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? StartIndex { get; set; }
-
- /// <summary>
- /// The maximum number of items to return
- /// </summary>
- /// <value>The limit.</value>
- [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? Limit { get; set; }
-
- [ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string Filters { get; set; }
-
- [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string Fields { get; set; }
-
- [ApiMember(Name = "ChannelIds", Description = "Optional. Specify one or more channel id's, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string ChannelIds { get; set; }
-
- /// <summary>
- /// Gets the filters.
- /// </summary>
- /// <returns>IEnumerable{ItemFilter}.</returns>
- public IEnumerable<ItemFilter> GetFilters()
- {
- return string.IsNullOrEmpty(Filters)
- ? Array.Empty<ItemFilter>()
- : Filters.Split(',').Select(v => Enum.Parse<ItemFilter>(v, true));
- }
- }
-
- [Authenticated]
- public class ChannelService : BaseApiService
- {
- private readonly IChannelManager _channelManager;
- private IUserManager _userManager;
-
- public ChannelService(
- ILogger<ChannelService> logger,
- IServerConfigurationManager serverConfigurationManager,
- IHttpResultFactory httpResultFactory,
- IChannelManager channelManager,
- IUserManager userManager)
- : base(logger, serverConfigurationManager, httpResultFactory)
- {
- _channelManager = channelManager;
- _userManager = userManager;
- }
-
- public object Get(GetAllChannelFeatures request)
- {
- var result = _channelManager.GetAllChannelFeatures();
-
- return ToOptimizedResult(result);
- }
-
- public object Get(GetChannelFeatures request)
- {
- var result = _channelManager.GetChannelFeatures(request.Id);
-
- return ToOptimizedResult(result);
- }
-
- public object Get(GetChannels request)
- {
- var result = _channelManager.GetChannels(new ChannelQuery
- {
- Limit = request.Limit,
- StartIndex = request.StartIndex,
- UserId = request.UserId,
- SupportsLatestItems = request.SupportsLatestItems,
- SupportsMediaDeletion = request.SupportsMediaDeletion,
- IsFavorite = request.IsFavorite
- });
-
- return ToOptimizedResult(result);
- }
-
- public async Task<object> Get(GetChannelItems request)
- {
- var user = request.UserId.Equals(Guid.Empty)
- ? null
- : _userManager.GetUserById(request.UserId);
-
- var query = new InternalItemsQuery(user)
- {
- Limit = request.Limit,
- StartIndex = request.StartIndex,
- ChannelIds = new[] { new Guid(request.Id) },
- ParentId = string.IsNullOrWhiteSpace(request.FolderId) ? Guid.Empty : new Guid(request.FolderId),
- OrderBy = request.GetOrderBy(),
- DtoOptions = new Controller.Dto.DtoOptions
- {
- Fields = request.GetItemFields()
- }
-
- };
-
- foreach (var filter in request.GetFilters())
- {
- 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 = await _channelManager.GetChannelItems(query, CancellationToken.None).ConfigureAwait(false);
-
- return ToOptimizedResult(result);
- }
-
- public async Task<object> Get(GetLatestChannelItems request)
- {
- var user = request.UserId.Equals(Guid.Empty)
- ? null
- : _userManager.GetUserById(request.UserId);
-
- var query = new InternalItemsQuery(user)
- {
- Limit = request.Limit,
- StartIndex = request.StartIndex,
- ChannelIds = (request.ChannelIds ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => new Guid(i)).ToArray(),
- DtoOptions = new Controller.Dto.DtoOptions
- {
- Fields = request.GetItemFields()
- }
- };
-
- foreach (var filter in request.GetFilters())
- {
- 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 = await _channelManager.GetLatestChannelItems(query, CancellationToken.None).ConfigureAwait(false);
-
- return ToOptimizedResult(result);
- }
- }
-}
diff --git a/MediaBrowser.Api/Devices/DeviceService.cs b/MediaBrowser.Api/Devices/DeviceService.cs
deleted file mode 100644
index dd3f3e738..000000000
--- a/MediaBrowser.Api/Devices/DeviceService.cs
+++ /dev/null
@@ -1,104 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Security;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Devices;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api.Devices
-{
- [Route("/Devices", "GET", Summary = "Gets all devices")]
- [Authenticated(Roles = "Admin")]
- public class GetDevices : DeviceQuery, IReturn<QueryResult<DeviceInfo>>
- {
- }
-
- [Route("/Devices/Info", "GET", Summary = "Gets info for a device")]
- [Authenticated(Roles = "Admin")]
- public class GetDeviceInfo : IReturn<DeviceInfo>
- {
- [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string Id { get; set; }
- }
-
- [Route("/Devices/Options", "GET", Summary = "Gets options for a device")]
- [Authenticated(Roles = "Admin")]
- public class GetDeviceOptions : IReturn<DeviceOptions>
- {
- [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string Id { get; set; }
- }
-
- [Route("/Devices", "DELETE", Summary = "Deletes a device")]
- public class DeleteDevice
- {
- [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
- public string Id { get; set; }
- }
-
- [Route("/Devices/Options", "POST", Summary = "Updates device options")]
- [Authenticated(Roles = "Admin")]
- public class PostDeviceOptions : DeviceOptions, IReturnVoid
- {
- [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
- public string Id { get; set; }
- }
-
- public class DeviceService : BaseApiService
- {
- private readonly IDeviceManager _deviceManager;
- private readonly IAuthenticationRepository _authRepo;
- private readonly ISessionManager _sessionManager;
-
- public DeviceService(
- ILogger<DeviceService> logger,
- IServerConfigurationManager serverConfigurationManager,
- IHttpResultFactory httpResultFactory,
- IDeviceManager deviceManager,
- IAuthenticationRepository authRepo,
- ISessionManager sessionManager)
- : base(logger, serverConfigurationManager, httpResultFactory)
- {
- _deviceManager = deviceManager;
- _authRepo = authRepo;
- _sessionManager = sessionManager;
- }
-
- public void Post(PostDeviceOptions request)
- {
- _deviceManager.UpdateDeviceOptions(request.Id, request);
- }
-
- public object Get(GetDevices request)
- {
- return ToOptimizedResult(_deviceManager.GetDevices(request));
- }
-
- public object Get(GetDeviceInfo request)
- {
- return _deviceManager.GetDevice(request.Id);
- }
-
- public object Get(GetDeviceOptions request)
- {
- return _deviceManager.GetDeviceOptions(request.Id);
- }
-
- public void Delete(DeleteDevice request)
- {
- var sessions = _authRepo.Get(new AuthenticationInfoQuery
- {
- DeviceId = request.Id
-
- }).Items;
-
- foreach (var session in sessions)
- {
- _sessionManager.Logout(session);
- }
- }
- }
-}
diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs
deleted file mode 100644
index 82d471412..000000000
--- a/MediaBrowser.Api/EnvironmentService.cs
+++ /dev/null
@@ -1,285 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api
-{
- /// <summary>
- /// Class GetDirectoryContents
- /// </summary>
- [Route("/Environment/DirectoryContents", "GET", Summary = "Gets the contents of a given directory in the file system")]
- public class GetDirectoryContents : IReturn<List<FileSystemEntryInfo>>
- {
- /// <summary>
- /// Gets or sets the path.
- /// </summary>
- /// <value>The path.</value>
- [ApiMember(Name = "Path", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string Path { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether [include files].
- /// </summary>
- /// <value><c>true</c> if [include files]; otherwise, <c>false</c>.</value>
- [ApiMember(Name = "IncludeFiles", Description = "An optional filter to include or exclude files from the results. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool IncludeFiles { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether [include directories].
- /// </summary>
- /// <value><c>true</c> if [include directories]; otherwise, <c>false</c>.</value>
- [ApiMember(Name = "IncludeDirectories", Description = "An optional filter to include or exclude folders from the results. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool IncludeDirectories { get; set; }
- }
-
- [Route("/Environment/ValidatePath", "POST", Summary = "Gets the contents of a given directory in the file system")]
- public class ValidatePath
- {
- /// <summary>
- /// Gets or sets the path.
- /// </summary>
- /// <value>The path.</value>
- [ApiMember(Name = "Path", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string Path { get; set; }
-
- public bool ValidateWriteable { get; set; }
- public bool? IsFile { get; set; }
- }
-
- [Obsolete]
- [Route("/Environment/NetworkShares", "GET", Summary = "Gets shares from a network device")]
- public class GetNetworkShares : IReturn<List<FileSystemEntryInfo>>
- {
- /// <summary>
- /// Gets or sets the path.
- /// </summary>
- /// <value>The path.</value>
- [ApiMember(Name = "Path", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string Path { get; set; }
- }
-
- /// <summary>
- /// Class GetDrives
- /// </summary>
- [Route("/Environment/Drives", "GET", Summary = "Gets available drives from the server's file system")]
- public class GetDrives : IReturn<List<FileSystemEntryInfo>>
- {
- }
-
- /// <summary>
- /// Class GetNetworkComputers
- /// </summary>
- [Route("/Environment/NetworkDevices", "GET", Summary = "Gets a list of devices on the network")]
- public class GetNetworkDevices : IReturn<List<FileSystemEntryInfo>>
- {
- }
-
- [Route("/Environment/ParentPath", "GET", Summary = "Gets the parent path of a given path")]
- public class GetParentPath : IReturn<string>
- {
- /// <summary>
- /// Gets or sets the path.
- /// </summary>
- /// <value>The path.</value>
- [ApiMember(Name = "Path", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string Path { get; set; }
- }
-
- public class DefaultDirectoryBrowserInfo
- {
- public string Path { get; set; }
- }
-
- [Route("/Environment/DefaultDirectoryBrowser", "GET", Summary = "Gets the parent path of a given path")]
- public class GetDefaultDirectoryBrowser : IReturn<DefaultDirectoryBrowserInfo>
- {
-
- }
-
- /// <summary>
- /// Class EnvironmentService
- /// </summary>
- [Authenticated(Roles = "Admin", AllowBeforeStartupWizard = true)]
- public class EnvironmentService : BaseApiService
- {
- private const char UncSeparator = '\\';
- private const string UncSeparatorString = "\\";
-
- /// <summary>
- /// The _network manager
- /// </summary>
- private readonly INetworkManager _networkManager;
- private readonly IFileSystem _fileSystem;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="EnvironmentService" /> class.
- /// </summary>
- /// <param name="networkManager">The network manager.</param>
- public EnvironmentService(
- ILogger<EnvironmentService> logger,
- IServerConfigurationManager serverConfigurationManager,
- IHttpResultFactory httpResultFactory,
- INetworkManager networkManager,
- IFileSystem fileSystem)
- : base(logger, serverConfigurationManager, httpResultFactory)
- {
- _networkManager = networkManager;
- _fileSystem = fileSystem;
- }
-
- public void Post(ValidatePath request)
- {
- if (request.IsFile.HasValue)
- {
- if (request.IsFile.Value)
- {
- if (!File.Exists(request.Path))
- {
- throw new FileNotFoundException("File not found", request.Path);
- }
- }
- else
- {
- if (!Directory.Exists(request.Path))
- {
- throw new FileNotFoundException("File not found", request.Path);
- }
- }
- }
-
- else
- {
- if (!File.Exists(request.Path) && !Directory.Exists(request.Path))
- {
- throw new FileNotFoundException("Path not found", request.Path);
- }
-
- if (request.ValidateWriteable)
- {
- EnsureWriteAccess(request.Path);
- }
- }
- }
-
- protected void EnsureWriteAccess(string path)
- {
- var file = Path.Combine(path, Guid.NewGuid().ToString());
-
- File.WriteAllText(file, string.Empty);
- _fileSystem.DeleteFile(file);
- }
-
- public object Get(GetDefaultDirectoryBrowser request) =>
- ToOptimizedResult(new DefaultDirectoryBrowserInfo { Path = null });
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetDirectoryContents request)
- {
- var path = request.Path;
-
- if (string.IsNullOrEmpty(path))
- {
- throw new ArgumentNullException(nameof(Path));
- }
-
- var networkPrefix = UncSeparatorString + UncSeparatorString;
-
- if (path.StartsWith(networkPrefix, StringComparison.OrdinalIgnoreCase)
- && path.LastIndexOf(UncSeparator) == 1)
- {
- return ToOptimizedResult(Array.Empty<FileSystemEntryInfo>());
- }
-
- return ToOptimizedResult(GetFileSystemEntries(request).ToList());
- }
-
- [Obsolete]
- public object Get(GetNetworkShares request)
- => ToOptimizedResult(Array.Empty<FileSystemEntryInfo>());
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetDrives request)
- {
- var result = GetDrives().ToList();
-
- return ToOptimizedResult(result);
- }
-
- /// <summary>
- /// Gets the list that is returned when an empty path is supplied
- /// </summary>
- /// <returns>IEnumerable{FileSystemEntryInfo}.</returns>
- private IEnumerable<FileSystemEntryInfo> GetDrives()
- {
- return _fileSystem.GetDrives().Select(d => new FileSystemEntryInfo(d.Name, d.FullName, FileSystemEntryType.Directory));
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetNetworkDevices request)
- => ToOptimizedResult(Array.Empty<FileSystemEntryInfo>());
-
- /// <summary>
- /// Gets the file system entries.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>IEnumerable{FileSystemEntryInfo}.</returns>
- private IEnumerable<FileSystemEntryInfo> GetFileSystemEntries(GetDirectoryContents request)
- {
- var entries = _fileSystem.GetFileSystemEntries(request.Path).OrderBy(i => i.FullName).Where(i =>
- {
- var isDirectory = i.IsDirectory;
-
- if (!request.IncludeFiles && !isDirectory)
- {
- return false;
- }
-
- return request.IncludeDirectories || !isDirectory;
- });
-
- return entries.Select(f => new FileSystemEntryInfo(f.Name, f.FullName, f.IsDirectory ? FileSystemEntryType.Directory : FileSystemEntryType.File));
- }
-
- public object Get(GetParentPath request)
- {
- var parent = Path.GetDirectoryName(request.Path);
-
- if (string.IsNullOrEmpty(parent))
- {
- // Check if unc share
- var index = request.Path.LastIndexOf(UncSeparator);
-
- if (index != -1 && request.Path.IndexOf(UncSeparator) == 0)
- {
- parent = request.Path.Substring(0, index);
-
- if (string.IsNullOrWhiteSpace(parent.TrimStart(UncSeparator)))
- {
- parent = null;
- }
- }
- }
-
- return parent;
- }
- }
-}
diff --git a/MediaBrowser.Api/IHasDtoOptions.cs b/MediaBrowser.Api/IHasDtoOptions.cs
index 03d3b3692..33d498e8b 100644
--- a/MediaBrowser.Api/IHasDtoOptions.cs
+++ b/MediaBrowser.Api/IHasDtoOptions.cs
@@ -3,6 +3,7 @@ namespace MediaBrowser.Api
public interface IHasDtoOptions : IHasItemFields
{
bool? EnableImages { get; set; }
+
bool? EnableUserData { get; set; }
int? ImageTypeLimit { get; set; }
diff --git a/MediaBrowser.Api/IHasItemFields.cs b/MediaBrowser.Api/IHasItemFields.cs
index 85b4a7e2d..ad4f1b489 100644
--- a/MediaBrowser.Api/IHasItemFields.cs
+++ b/MediaBrowser.Api/IHasItemFields.cs
@@ -5,7 +5,7 @@ using MediaBrowser.Model.Querying;
namespace MediaBrowser.Api
{
/// <summary>
- /// Interface IHasItemFields
+ /// Interface IHasItemFields.
/// </summary>
public interface IHasItemFields
{
@@ -43,7 +43,6 @@ namespace MediaBrowser.Api
}
return null;
-
}).Where(i => i.HasValue).Select(i => i.Value).ToArray();
}
}
diff --git a/MediaBrowser.Api/Images/ImageRequest.cs b/MediaBrowser.Api/Images/ImageRequest.cs
index 71ff09b63..0f3455548 100644
--- a/MediaBrowser.Api/Images/ImageRequest.cs
+++ b/MediaBrowser.Api/Images/ImageRequest.cs
@@ -4,30 +4,30 @@ using MediaBrowser.Model.Services;
namespace MediaBrowser.Api.Images
{
/// <summary>
- /// Class ImageRequest
+ /// Class ImageRequest.
/// </summary>
public class ImageRequest : DeleteImageRequest
{
/// <summary>
- /// The max width
+ /// The max width.
/// </summary>
[ApiMember(Name = "MaxWidth", Description = "The maximum image width to return.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? MaxWidth { get; set; }
/// <summary>
- /// The max height
+ /// The max height.
/// </summary>
[ApiMember(Name = "MaxHeight", Description = "The maximum image height to return.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? MaxHeight { get; set; }
/// <summary>
- /// The width
+ /// The width.
/// </summary>
[ApiMember(Name = "Width", Description = "The fixed image width to return.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? Width { get; set; }
/// <summary>
- /// The height
+ /// The height.
/// </summary>
[ApiMember(Name = "Height", Description = "The fixed image height to return.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? Height { get; set; }
@@ -79,7 +79,7 @@ namespace MediaBrowser.Api.Images
}
/// <summary>
- /// Class DeleteImageRequest
+ /// Class DeleteImageRequest.
/// </summary>
public class DeleteImageRequest
{
diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs
index a98266e0d..55859d9f1 100644
--- a/MediaBrowser.Api/Images/ImageService.cs
+++ b/MediaBrowser.Api/Images/ImageService.cs
@@ -41,7 +41,7 @@ namespace MediaBrowser.Api.Images
}
/// <summary>
- /// Class GetPersonImage
+ /// Class GetPersonImage.
/// </summary>
[Route("/Artists/{Name}/Images/{Type}", "GET")]
[Route("/Artists/{Name}/Images/{Type}/{Index}", "GET")]
@@ -78,7 +78,7 @@ namespace MediaBrowser.Api.Images
}
/// <summary>
- /// Class GetUserImage
+ /// Class GetUserImage.
/// </summary>
[Route("/Users/{Id}/Images/{Type}", "GET")]
[Route("/Users/{Id}/Images/{Type}/{Index}", "GET")]
@@ -95,7 +95,7 @@ namespace MediaBrowser.Api.Images
}
/// <summary>
- /// Class ImageService
+ /// Class ImageService.
/// </summary>
public class ImageService : BaseApiService
{
@@ -405,7 +405,6 @@ namespace MediaBrowser.Api.Images
Path = imageResult.Item1,
FileShare = FileShare.Read
-
}).ConfigureAwait(false);
}
@@ -558,6 +557,11 @@ namespace MediaBrowser.Api.Images
// Handle image/png; charset=utf-8
mimeType = mimeType.Split(';').FirstOrDefault();
var userDataPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, user.Username);
+ if (user.ProfileImage != null)
+ {
+ _userManager.ClearProfileImage(user);
+ }
+
user.ProfileImage = new Jellyfin.Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType)));
await _providerManager
diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs
deleted file mode 100644
index 279fd6ee9..000000000
--- a/MediaBrowser.Api/LiveTv/LiveTvService.cs
+++ /dev/null
@@ -1,1279 +0,0 @@
-using System;
-using System.Collections.Generic;
-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 MediaBrowser.Api.UserLibrary;
-using MediaBrowser.Common;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.LiveTv;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-using Microsoft.Net.Http.Headers;
-
-namespace MediaBrowser.Api.LiveTv
-{
- /// <summary>
- /// This is insecure right now to avoid windows phone refactoring
- /// </summary>
- [Route("/LiveTv/Info", "GET", Summary = "Gets available live tv services.")]
- [Authenticated]
- public class GetLiveTvInfo : IReturn<LiveTvInfo>
- {
- }
-
- [Route("/LiveTv/Channels", "GET", Summary = "Gets available live tv channels.")]
- [Authenticated]
- public class GetChannels : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions
- {
- [ApiMember(Name = "Type", Description = "Optional filter by channel type.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public ChannelType? Type { get; set; }
-
- [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public Guid UserId { get; set; }
-
- /// <summary>
- /// Skips over a given number of items within the results. Use for paging.
- /// </summary>
- /// <value>The start index.</value>
- [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? StartIndex { get; set; }
-
- [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
- public bool? IsMovie { get; set; }
-
- [ApiMember(Name = "IsSeries", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
- public bool? IsSeries { get; set; }
-
- [ApiMember(Name = "IsNews", Description = "Optional filter for news.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
- public bool? IsNews { get; set; }
-
- [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
- public bool? IsKids { get; set; }
-
- [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
- public bool? IsSports { get; set; }
-
- /// <summary>
- /// The maximum number of items to return
- /// </summary>
- /// <value>The limit.</value>
- [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? Limit { get; set; }
-
- [ApiMember(Name = "IsFavorite", Description = "Filter by channels that are favorites, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? IsFavorite { get; set; }
-
- [ApiMember(Name = "IsLiked", Description = "Filter by channels that are liked, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? IsLiked { get; set; }
-
- [ApiMember(Name = "IsDisliked", Description = "Filter by channels that are disliked, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? IsDisliked { get; set; }
-
- [ApiMember(Name = "EnableFavoriteSorting", Description = "Incorporate favorite and like status into channel sorting.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool EnableFavoriteSorting { get; set; }
-
- [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? EnableImages { get; set; }
-
- [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? ImageTypeLimit { get; set; }
-
- [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string EnableImageTypes { get; set; }
-
- /// <summary>
- /// Fields to return within the items, in addition to basic information
- /// </summary>
- /// <value>The fields.</value>
- [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string Fields { get; set; }
-
- [ApiMember(Name = "AddCurrentProgram", Description = "Optional. Adds current program info to each channel", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public bool AddCurrentProgram { get; set; }
-
- [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? EnableUserData { get; set; }
-
- public string SortBy { get; set; }
-
- public SortOrder? SortOrder { get; set; }
-
- /// <summary>
- /// Gets the order by.
- /// </summary>
- /// <returns>IEnumerable{ItemSortBy}.</returns>
- public string[] GetOrderBy()
- {
- var val = SortBy;
-
- if (string.IsNullOrEmpty(val))
- {
- return Array.Empty<string>();
- }
-
- return val.Split(',');
- }
-
- public GetChannels()
- {
- AddCurrentProgram = true;
- }
- }
-
- [Route("/LiveTv/Channels/{Id}", "GET", Summary = "Gets a live tv channel")]
- [Authenticated]
- public class GetChannel : IReturn<BaseItemDto>
- {
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
-
- [ApiMember(Name = "UserId", Description = "Optional attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public Guid UserId { get; set; }
- }
-
- [Route("/LiveTv/Recordings", "GET", Summary = "Gets live tv recordings")]
- [Authenticated]
- public class GetRecordings : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions
- {
- [ApiMember(Name = "ChannelId", Description = "Optional filter by channel id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string ChannelId { get; set; }
-
- [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public Guid UserId { get; set; }
-
- [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? StartIndex { get; set; }
-
- [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? Limit { get; set; }
-
- [ApiMember(Name = "Status", Description = "Optional filter by recording status.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public RecordingStatus? Status { get; set; }
-
- [ApiMember(Name = "Status", Description = "Optional filter by recordings that are in progress, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? IsInProgress { get; set; }
-
- [ApiMember(Name = "SeriesTimerId", Description = "Optional filter by recordings belonging to a series timer", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string SeriesTimerId { get; set; }
-
- [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? EnableImages { get; set; }
-
- [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? ImageTypeLimit { get; set; }
-
- [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string EnableImageTypes { get; set; }
-
- /// <summary>
- /// Fields to return within the items, in addition to basic information
- /// </summary>
- /// <value>The fields.</value>
- [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string Fields { get; set; }
-
- public bool EnableTotalRecordCount { get; set; }
-
- [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? EnableUserData { get; set; }
-
- public bool? IsMovie { get; set; }
- public bool? IsSeries { get; set; }
- public bool? IsKids { get; set; }
- public bool? IsSports { get; set; }
- public bool? IsNews { get; set; }
- public bool? IsLibraryItem { get; set; }
-
- public GetRecordings()
- {
- EnableTotalRecordCount = true;
- }
- }
-
- [Route("/LiveTv/Recordings/Series", "GET", Summary = "Gets live tv recordings")]
- [Authenticated]
- public class GetRecordingSeries : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions
- {
- [ApiMember(Name = "ChannelId", Description = "Optional filter by channel id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string ChannelId { get; set; }
-
- [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string UserId { get; set; }
-
- [ApiMember(Name = "GroupId", Description = "Optional filter by recording group.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string GroupId { get; set; }
-
- [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? StartIndex { get; set; }
-
- [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? Limit { get; set; }
-
- [ApiMember(Name = "Status", Description = "Optional filter by recording status.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public RecordingStatus? Status { get; set; }
-
- [ApiMember(Name = "Status", Description = "Optional filter by recordings that are in progress, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? IsInProgress { get; set; }
-
- [ApiMember(Name = "SeriesTimerId", Description = "Optional filter by recordings belonging to a series timer", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string SeriesTimerId { get; set; }
-
- [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? EnableImages { get; set; }
-
- [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? ImageTypeLimit { get; set; }
-
- [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string EnableImageTypes { get; set; }
-
- /// <summary>
- /// Fields to return within the items, in addition to basic information
- /// </summary>
- /// <value>The fields.</value>
- [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string Fields { get; set; }
-
- public bool EnableTotalRecordCount { get; set; }
-
- [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? EnableUserData { get; set; }
-
- public GetRecordingSeries()
- {
- EnableTotalRecordCount = true;
- }
- }
-
- [Route("/LiveTv/Recordings/Groups", "GET", Summary = "Gets live tv recording groups")]
- [Authenticated]
- public class GetRecordingGroups : IReturn<QueryResult<BaseItemDto>>
- {
- [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string UserId { get; set; }
- }
-
- [Route("/LiveTv/Recordings/Folders", "GET", Summary = "Gets recording folders")]
- [Authenticated]
- public class GetRecordingFolders : IReturn<BaseItemDto[]>
- {
- [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public Guid UserId { get; set; }
- }
-
- [Route("/LiveTv/Recordings/{Id}", "GET", Summary = "Gets a live tv recording")]
- [Authenticated]
- public class GetRecording : IReturn<BaseItemDto>
- {
- [ApiMember(Name = "Id", Description = "Recording Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
-
- [ApiMember(Name = "UserId", Description = "Optional attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public Guid UserId { get; set; }
- }
-
- [Route("/LiveTv/Tuners/{Id}/Reset", "POST", Summary = "Resets a tv tuner")]
- [Authenticated]
- public class ResetTuner : IReturnVoid
- {
- [ApiMember(Name = "Id", Description = "Tuner Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
- }
-
- [Route("/LiveTv/Timers/{Id}", "GET", Summary = "Gets a live tv timer")]
- [Authenticated]
- public class GetTimer : IReturn<TimerInfoDto>
- {
- [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
- }
-
- [Route("/LiveTv/Timers/Defaults", "GET", Summary = "Gets default values for a new timer")]
- [Authenticated]
- public class GetDefaultTimer : IReturn<SeriesTimerInfoDto>
- {
- [ApiMember(Name = "ProgramId", Description = "Optional, to attach default values based on a program.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string ProgramId { get; set; }
- }
-
- [Route("/LiveTv/Timers", "GET", Summary = "Gets live tv timers")]
- [Authenticated]
- public class GetTimers : IReturn<QueryResult<TimerInfoDto>>
- {
- [ApiMember(Name = "ChannelId", Description = "Optional filter by channel id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string ChannelId { get; set; }
-
- [ApiMember(Name = "SeriesTimerId", Description = "Optional filter by timers belonging to a series timer", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string SeriesTimerId { get; set; }
-
- public bool? IsActive { get; set; }
-
- public bool? IsScheduled { get; set; }
- }
-
- [Route("/LiveTv/Programs", "GET,POST", Summary = "Gets available live tv epgs..")]
- [Authenticated]
- public class GetPrograms : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions
- {
- [ApiMember(Name = "ChannelIds", Description = "The channels to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
- public string ChannelIds { get; set; }
-
- [ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
- public Guid UserId { get; set; }
-
- [ApiMember(Name = "MinStartDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
- public string MinStartDate { get; set; }
-
- [ApiMember(Name = "HasAired", Description = "Optional. Filter by programs that have completed airing, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? HasAired { get; set; }
- public bool? IsAiring { get; set; }
-
- [ApiMember(Name = "MaxStartDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
- public string MaxStartDate { get; set; }
-
- [ApiMember(Name = "MinEndDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
- public string MinEndDate { get; set; }
-
- [ApiMember(Name = "MaxEndDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
- public string MaxEndDate { get; set; }
-
- [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
- public bool? IsMovie { get; set; }
-
- [ApiMember(Name = "IsSeries", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
- public bool? IsSeries { get; set; }
-
- [ApiMember(Name = "IsNews", Description = "Optional filter for news.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
- public bool? IsNews { get; set; }
-
- [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
- public bool? IsKids { get; set; }
-
- [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
- public bool? IsSports { get; set; }
-
- [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? StartIndex { get; set; }
-
- [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? Limit { get; set; }
-
- [ApiMember(Name = "SortBy", Description = "Optional. Specify one or more sort orders, comma delimeted. Options: Name, StartDate", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string SortBy { get; set; }
-
- [ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string SortOrder { get; set; }
-
- [ApiMember(Name = "Genres", Description = "The genres to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
- public string Genres { get; set; }
-
- [ApiMember(Name = "GenreIds", Description = "The genres to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
- public string GenreIds { get; set; }
-
- [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? EnableImages { get; set; }
-
- public bool EnableTotalRecordCount { get; set; }
-
- [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? ImageTypeLimit { get; set; }
-
- [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string EnableImageTypes { get; set; }
-
- [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? EnableUserData { get; set; }
-
- public string SeriesTimerId { get; set; }
- public Guid LibrarySeriesId { get; set; }
-
- /// <summary>
- /// Fields to return within the items, in addition to basic information
- /// </summary>
- /// <value>The fields.</value>
- [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string Fields { get; set; }
-
- public GetPrograms()
- {
- EnableTotalRecordCount = true;
- }
- }
-
- [Route("/LiveTv/Programs/Recommended", "GET", Summary = "Gets available live tv epgs..")]
- [Authenticated]
- public class GetRecommendedPrograms : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions
- {
- public bool EnableTotalRecordCount { get; set; }
-
- public GetRecommendedPrograms()
- {
- EnableTotalRecordCount = true;
- }
-
- [ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
- public Guid UserId { get; set; }
-
- [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? Limit { get; set; }
-
- [ApiMember(Name = "IsAiring", Description = "Optional. Filter by programs that are currently airing, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? IsAiring { get; set; }
-
- [ApiMember(Name = "HasAired", Description = "Optional. Filter by programs that have completed airing, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? HasAired { get; set; }
-
- [ApiMember(Name = "IsSeries", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
- public bool? IsSeries { get; set; }
-
- [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
- public bool? IsMovie { get; set; }
-
- [ApiMember(Name = "IsNews", Description = "Optional filter for news.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
- public bool? IsNews { get; set; }
-
- [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
- public bool? IsKids { get; set; }
-
- [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
- public bool? IsSports { get; set; }
-
- [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? EnableImages { get; set; }
-
- [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? ImageTypeLimit { get; set; }
-
- [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string EnableImageTypes { get; set; }
-
- [ApiMember(Name = "GenreIds", Description = "The genres to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
- public string GenreIds { get; set; }
-
- /// <summary>
- /// Fields to return within the items, in addition to basic information
- /// </summary>
- /// <value>The fields.</value>
- [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string Fields { get; set; }
-
- [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? EnableUserData { get; set; }
- }
-
- [Route("/LiveTv/Programs/{Id}", "GET", Summary = "Gets a live tv program")]
- [Authenticated]
- public class GetProgram : IReturn<BaseItemDto>
- {
- [ApiMember(Name = "Id", Description = "Program Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
-
- [ApiMember(Name = "UserId", Description = "Optional attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public Guid UserId { get; set; }
- }
-
-
- [Route("/LiveTv/Recordings/{Id}", "DELETE", Summary = "Deletes a live tv recording")]
- [Authenticated]
- public class DeleteRecording : IReturnVoid
- {
- [ApiMember(Name = "Id", Description = "Recording Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public Guid Id { get; set; }
- }
-
- [Route("/LiveTv/Timers/{Id}", "DELETE", Summary = "Cancels a live tv timer")]
- [Authenticated]
- public class CancelTimer : IReturnVoid
- {
- [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public string Id { get; set; }
- }
-
- [Route("/LiveTv/Timers/{Id}", "POST", Summary = "Updates a live tv timer")]
- [Authenticated]
- public class UpdateTimer : TimerInfoDto, IReturnVoid
- {
- }
-
- [Route("/LiveTv/Timers", "POST", Summary = "Creates a live tv timer")]
- [Authenticated]
- public class CreateTimer : TimerInfoDto, IReturnVoid
- {
- }
-
- [Route("/LiveTv/SeriesTimers/{Id}", "GET", Summary = "Gets a live tv series timer")]
- [Authenticated]
- public class GetSeriesTimer : IReturn<TimerInfoDto>
- {
- [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
- }
-
- [Route("/LiveTv/SeriesTimers", "GET", Summary = "Gets live tv series timers")]
- [Authenticated]
- public class GetSeriesTimers : IReturn<QueryResult<SeriesTimerInfoDto>>
- {
- [ApiMember(Name = "SortBy", Description = "Optional. Sort by SortName or Priority", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
- public string SortBy { get; set; }
-
- [ApiMember(Name = "SortOrder", Description = "Optional. Sort in Ascending or Descending order", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
- public SortOrder SortOrder { get; set; }
- }
-
- [Route("/LiveTv/SeriesTimers/{Id}", "DELETE", Summary = "Cancels a live tv series timer")]
- [Authenticated]
- public class CancelSeriesTimer : IReturnVoid
- {
- [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public string Id { get; set; }
- }
-
- [Route("/LiveTv/SeriesTimers/{Id}", "POST", Summary = "Updates a live tv series timer")]
- [Authenticated]
- public class UpdateSeriesTimer : SeriesTimerInfoDto, IReturnVoid
- {
- }
-
- [Route("/LiveTv/SeriesTimers", "POST", Summary = "Creates a live tv series timer")]
- [Authenticated]
- public class CreateSeriesTimer : SeriesTimerInfoDto, IReturnVoid
- {
- }
-
- [Route("/LiveTv/Recordings/Groups/{Id}", "GET", Summary = "Gets a recording group")]
- [Authenticated]
- public class GetRecordingGroup : IReturn<BaseItemDto>
- {
- [ApiMember(Name = "Id", Description = "Recording group Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
- }
-
- [Route("/LiveTv/GuideInfo", "GET", Summary = "Gets guide info")]
- [Authenticated]
- public class GetGuideInfo : IReturn<GuideInfo>
- {
- }
-
- [Route("/LiveTv/TunerHosts", "POST", Summary = "Adds a tuner host")]
- [Authenticated]
- public class AddTunerHost : TunerHostInfo, IReturn<TunerHostInfo>
- {
- }
-
- [Route("/LiveTv/TunerHosts", "DELETE", Summary = "Deletes a tuner host")]
- [Authenticated]
- public class DeleteTunerHost : IReturnVoid
- {
- [ApiMember(Name = "Id", Description = "Tuner host id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "DELETE")]
- public string Id { get; set; }
- }
-
- [Route("/LiveTv/ListingProviders/Default", "GET")]
- [Authenticated]
- public class GetDefaultListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo>
- {
- }
-
- [Route("/LiveTv/ListingProviders", "POST", Summary = "Adds a listing provider")]
- [Authenticated]
- public class AddListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo>
- {
- public bool ValidateLogin { get; set; }
- public bool ValidateListings { get; set; }
- public string Pw { get; set; }
- }
-
- [Route("/LiveTv/ListingProviders", "DELETE", Summary = "Deletes a listing provider")]
- [Authenticated]
- public class DeleteListingProvider : IReturnVoid
- {
- [ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "DELETE")]
- public string Id { get; set; }
- }
-
- [Route("/LiveTv/ListingProviders/Lineups", "GET", Summary = "Gets available lineups")]
- [Authenticated]
- public class GetLineups : IReturn<List<NameIdPair>>
- {
- [ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string Id { get; set; }
-
- [ApiMember(Name = "Type", Description = "Provider Type", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string Type { get; set; }
-
- [ApiMember(Name = "Location", Description = "Location", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string Location { get; set; }
-
- [ApiMember(Name = "Country", Description = "Country", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string Country { get; set; }
- }
-
- [Route("/LiveTv/ListingProviders/SchedulesDirect/Countries", "GET", Summary = "Gets available lineups")]
- [Authenticated]
- public class GetSchedulesDirectCountries
- {
- }
-
- [Route("/LiveTv/ChannelMappingOptions")]
- [Authenticated]
- public class GetChannelMappingOptions
- {
- [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query")]
- public string ProviderId { get; set; }
- }
-
- [Route("/LiveTv/ChannelMappings")]
- [Authenticated]
- public class SetChannelMapping
- {
- [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query")]
- public string ProviderId { get; set; }
- public string TunerChannelId { get; set; }
- public string ProviderChannelId { get; set; }
- }
-
- public class ChannelMappingOptions
- {
- public List<TunerChannelMapping> TunerChannels { get; set; }
- public List<NameIdPair> ProviderChannels { get; set; }
- public NameValuePair[] Mappings { get; set; }
- public string ProviderName { get; set; }
- }
-
- [Route("/LiveTv/LiveStreamFiles/{Id}/stream.{Container}", "GET", Summary = "Gets a live tv channel")]
- public class GetLiveStreamFile
- {
- public string Id { get; set; }
- public string Container { get; set; }
- }
-
- [Route("/LiveTv/LiveRecordings/{Id}/stream", "GET", Summary = "Gets a live tv channel")]
- public class GetLiveRecordingFile
- {
- public string Id { get; set; }
- }
-
- [Route("/LiveTv/TunerHosts/Types", "GET")]
- [Authenticated]
- public class GetTunerHostTypes : IReturn<List<NameIdPair>>
- {
-
- }
-
- [Route("/LiveTv/Tuners/Discvover", "GET")]
- [Authenticated]
- public class DiscoverTuners : IReturn<List<TunerHostInfo>>
- {
- public bool NewDevicesOnly { get; set; }
- }
-
- public class LiveTvService : BaseApiService
- {
- private readonly ILiveTvManager _liveTvManager;
- private readonly IUserManager _userManager;
- private readonly IHttpClient _httpClient;
- private readonly ILibraryManager _libraryManager;
- private readonly IDtoService _dtoService;
- private readonly IAuthorizationContext _authContext;
- private readonly ISessionContext _sessionContext;
- private readonly IStreamHelper _streamHelper;
- private readonly IMediaSourceManager _mediaSourceManager;
-
- public LiveTvService(
- ILogger<LiveTvService> logger,
- IServerConfigurationManager serverConfigurationManager,
- IHttpResultFactory httpResultFactory,
- IMediaSourceManager mediaSourceManager,
- IStreamHelper streamHelper,
- ILiveTvManager liveTvManager,
- IUserManager userManager,
- IHttpClient httpClient,
- ILibraryManager libraryManager,
- IDtoService dtoService,
- IAuthorizationContext authContext,
- ISessionContext sessionContext)
- : base(logger, serverConfigurationManager, httpResultFactory)
- {
- _mediaSourceManager = mediaSourceManager;
- _streamHelper = streamHelper;
- _liveTvManager = liveTvManager;
- _userManager = userManager;
- _httpClient = httpClient;
- _libraryManager = libraryManager;
- _dtoService = dtoService;
- _authContext = authContext;
- _sessionContext = sessionContext;
- }
-
- public object Get(GetTunerHostTypes request)
- {
- var list = _liveTvManager.GetTunerHostTypes();
- return ToOptimizedResult(list);
- }
-
- public object Get(GetRecordingFolders request)
- {
- var user = request.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(request.UserId);
- var folders = _liveTvManager.GetRecordingFolders(user);
-
- var returnArray = _dtoService.GetBaseItemDtos(folders, new DtoOptions(), user);
-
- var result = new QueryResult<BaseItemDto>
- {
- Items = returnArray,
- TotalRecordCount = returnArray.Count
- };
-
- return ToOptimizedResult(result);
- }
-
- public object Get(GetLiveRecordingFile request)
- {
- var path = _liveTvManager.GetEmbyTvActiveRecordingPath(request.Id);
-
- if (string.IsNullOrWhiteSpace(path))
- {
- throw new FileNotFoundException();
- }
-
- var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
- {
- [HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType(path)
- };
-
- return new ProgressiveFileCopier(_streamHelper, path, outputHeaders, Logger)
- {
- AllowEndOfFile = false
- };
- }
-
- public async Task<object> Get(DiscoverTuners request)
- {
- var result = await _liveTvManager.DiscoverTuners(request.NewDevicesOnly, CancellationToken.None).ConfigureAwait(false);
- return ToOptimizedResult(result);
- }
-
- public async Task<object> Get(GetLiveStreamFile request)
- {
- var liveStreamInfo = await _mediaSourceManager.GetDirectStreamProviderByUniqueId(request.Id, CancellationToken.None).ConfigureAwait(false);
-
- var directStreamProvider = liveStreamInfo;
-
- var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
- {
- [HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType("file." + request.Container)
- };
-
- return new ProgressiveFileCopier(directStreamProvider, _streamHelper, outputHeaders, Logger)
- {
- AllowEndOfFile = false
- };
- }
-
- public object Get(GetDefaultListingProvider request)
- {
- return ToOptimizedResult(new ListingsProviderInfo());
- }
-
- public async Task<object> Post(SetChannelMapping request)
- {
- return await _liveTvManager.SetChannelMapping(request.ProviderId, request.TunerChannelId, request.ProviderChannelId).ConfigureAwait(false);
- }
-
- public async Task<object> Get(GetChannelMappingOptions request)
- {
- var config = GetConfiguration();
-
- var listingsProviderInfo = config.ListingProviders.First(i => string.Equals(request.ProviderId, i.Id, StringComparison.OrdinalIgnoreCase));
-
- var listingsProviderName = _liveTvManager.ListingProviders.First(i => string.Equals(i.Type, listingsProviderInfo.Type, StringComparison.OrdinalIgnoreCase)).Name;
-
- var tunerChannels = await _liveTvManager.GetChannelsForListingsProvider(request.ProviderId, CancellationToken.None)
- .ConfigureAwait(false);
-
- var providerChannels = await _liveTvManager.GetChannelsFromListingsProviderData(request.ProviderId, CancellationToken.None)
- .ConfigureAwait(false);
-
- var mappings = listingsProviderInfo.ChannelMappings;
-
- var result = new ChannelMappingOptions
- {
- TunerChannels = tunerChannels.Select(i => _liveTvManager.GetTunerChannelMapping(i, mappings, providerChannels)).ToList(),
-
- ProviderChannels = providerChannels.Select(i => new NameIdPair
- {
- Name = i.Name,
- Id = i.Id
-
- }).ToList(),
-
- Mappings = mappings,
-
- ProviderName = listingsProviderName
- };
-
- return ToOptimizedResult(result);
- }
-
- public async Task<object> Get(GetSchedulesDirectCountries request)
- {
- // https://json.schedulesdirect.org/20141201/available/countries
-
- var response = await _httpClient.Get(new HttpRequestOptions
- {
- Url = "https://json.schedulesdirect.org/20141201/available/countries",
- BufferContent = false
-
- }).ConfigureAwait(false);
-
- return ResultFactory.GetResult(Request, response, "application/json");
- }
-
- private void AssertUserCanManageLiveTv()
- {
- var user = _sessionContext.GetUser(Request);
-
- if (user == null)
- {
- throw new SecurityException("Anonymous live tv management is not allowed.");
- }
-
- if (!user.HasPermission(PermissionKind.EnableLiveTvManagement))
- {
- throw new SecurityException("The current user does not have permission to manage live tv.");
- }
- }
-
- public async Task<object> Post(AddListingProvider request)
- {
- if (request.Pw != null)
- {
- request.Password = GetHashedString(request.Pw);
- }
-
- request.Pw = null;
-
- var result = await _liveTvManager.SaveListingProvider(request, request.ValidateLogin, request.ValidateListings).ConfigureAwait(false);
- return ToOptimizedResult(result);
- }
-
- /// <summary>
- /// Gets the hashed string.
- /// </summary>
- private string GetHashedString(string str)
- {
- // SchedulesDirect requires a SHA1 hash of the user's password
- // https://github.com/SchedulesDirect/JSON-Service/wiki/API-20141201#obtain-a-token
- using SHA1 sha = SHA1.Create();
-
- return Hex.Encode(
- sha.ComputeHash(Encoding.UTF8.GetBytes(str)));
- }
-
- public void Delete(DeleteListingProvider request)
- {
- _liveTvManager.DeleteListingsProvider(request.Id);
- }
-
- public async Task<object> Post(AddTunerHost request)
- {
- var result = await _liveTvManager.SaveTunerHost(request).ConfigureAwait(false);
- return ToOptimizedResult(result);
- }
-
- public void Delete(DeleteTunerHost request)
- {
- var config = GetConfiguration();
-
- config.TunerHosts = config.TunerHosts.Where(i => !string.Equals(request.Id, i.Id, StringComparison.OrdinalIgnoreCase)).ToArray();
-
- ServerConfigurationManager.SaveConfiguration("livetv", config);
- }
-
- private LiveTvOptions GetConfiguration()
- {
- return ServerConfigurationManager.GetConfiguration<LiveTvOptions>("livetv");
- }
-
- private void UpdateConfiguration(LiveTvOptions options)
- {
- ServerConfigurationManager.SaveConfiguration("livetv", options);
- }
-
- public async Task<object> Get(GetLineups request)
- {
- var info = await _liveTvManager.GetLineups(request.Type, request.Id, request.Country, request.Location).ConfigureAwait(false);
-
- return ToOptimizedResult(info);
- }
-
- public object Get(GetLiveTvInfo request)
- {
- var info = _liveTvManager.GetLiveTvInfo(CancellationToken.None);
-
- return ToOptimizedResult(info);
- }
-
- public object Get(GetChannels request)
- {
- var options = GetDtoOptions(_authContext, request);
-
- var channelResult = _liveTvManager.GetInternalChannels(new LiveTvChannelQuery
- {
- ChannelType = request.Type,
- UserId = request.UserId,
- StartIndex = request.StartIndex,
- Limit = request.Limit,
- IsFavorite = request.IsFavorite,
- IsLiked = request.IsLiked,
- IsDisliked = request.IsDisliked,
- EnableFavoriteSorting = request.EnableFavoriteSorting,
- IsMovie = request.IsMovie,
- IsSeries = request.IsSeries,
- IsNews = request.IsNews,
- IsKids = request.IsKids,
- IsSports = request.IsSports,
- SortBy = request.GetOrderBy(),
- SortOrder = request.SortOrder ?? SortOrder.Ascending,
- AddCurrentProgram = request.AddCurrentProgram
-
- }, options, CancellationToken.None);
-
- var user = request.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(request.UserId);
-
- RemoveFields(options);
-
- options.AddCurrentProgram = request.AddCurrentProgram;
-
- var returnArray = _dtoService.GetBaseItemDtos(channelResult.Items, options, user);
-
- var result = new QueryResult<BaseItemDto>
- {
- Items = returnArray,
- TotalRecordCount = channelResult.TotalRecordCount
- };
-
- return ToOptimizedResult(result);
- }
-
- private void RemoveFields(DtoOptions options)
- {
- var fields = options.Fields.ToList();
-
- fields.Remove(ItemFields.CanDelete);
- fields.Remove(ItemFields.CanDownload);
- fields.Remove(ItemFields.DisplayPreferencesId);
- fields.Remove(ItemFields.Etag);
- options.Fields = fields.ToArray();
- }
-
- public object Get(GetChannel request)
- {
- var user = _userManager.GetUserById(request.UserId);
-
- var item = string.IsNullOrEmpty(request.Id) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(request.Id);
-
- var dtoOptions = GetDtoOptions(_authContext, request);
-
- var result = _dtoService.GetBaseItemDto(item, dtoOptions, user);
-
- return ToOptimizedResult(result);
- }
-
- public async Task<object> Get(GetPrograms request)
- {
- var user = request.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(request.UserId);
-
- var query = new InternalItemsQuery(user)
- {
- ChannelIds = ApiEntryPoint.Split(request.ChannelIds, ',', true).Select(i => new Guid(i)).ToArray(),
- HasAired = request.HasAired,
- IsAiring = request.IsAiring,
- EnableTotalRecordCount = request.EnableTotalRecordCount
- };
-
- if (!string.IsNullOrEmpty(request.MinStartDate))
- {
- query.MinStartDate = DateTime.Parse(request.MinStartDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
- }
-
- if (!string.IsNullOrEmpty(request.MinEndDate))
- {
- query.MinEndDate = DateTime.Parse(request.MinEndDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
- }
-
- if (!string.IsNullOrEmpty(request.MaxStartDate))
- {
- query.MaxStartDate = DateTime.Parse(request.MaxStartDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
- }
-
- if (!string.IsNullOrEmpty(request.MaxEndDate))
- {
- query.MaxEndDate = DateTime.Parse(request.MaxEndDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
- }
-
- query.StartIndex = request.StartIndex;
- query.Limit = request.Limit;
- query.OrderBy = BaseItemsRequest.GetOrderBy(request.SortBy, request.SortOrder);
- query.IsNews = request.IsNews;
- query.IsMovie = request.IsMovie;
- query.IsSeries = request.IsSeries;
- query.IsKids = request.IsKids;
- query.IsSports = request.IsSports;
- query.SeriesTimerId = request.SeriesTimerId;
- query.Genres = (request.Genres ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
- query.GenreIds = GetGuids(request.GenreIds);
-
- if (!request.LibrarySeriesId.Equals(Guid.Empty))
- {
- query.IsSeries = true;
-
- if (_libraryManager.GetItemById(request.LibrarySeriesId) is Series series)
- {
- query.Name = series.Name;
- }
- }
-
- var result = await _liveTvManager.GetPrograms(query, GetDtoOptions(_authContext, request), CancellationToken.None).ConfigureAwait(false);
-
- return ToOptimizedResult(result);
- }
-
- public object Get(GetRecommendedPrograms request)
- {
- var user = _userManager.GetUserById(request.UserId);
-
- var query = new InternalItemsQuery(user)
- {
- IsAiring = request.IsAiring,
- Limit = request.Limit,
- HasAired = request.HasAired,
- IsSeries = request.IsSeries,
- IsMovie = request.IsMovie,
- IsKids = request.IsKids,
- IsNews = request.IsNews,
- IsSports = request.IsSports,
- EnableTotalRecordCount = request.EnableTotalRecordCount
- };
-
- query.GenreIds = GetGuids(request.GenreIds);
-
- var result = _liveTvManager.GetRecommendedPrograms(query, GetDtoOptions(_authContext, request), CancellationToken.None);
-
- return ToOptimizedResult(result);
- }
-
- public object Post(GetPrograms request)
- {
- return Get(request);
- }
-
- public object Get(GetRecordings request)
- {
- var options = GetDtoOptions(_authContext, request);
-
- var result = _liveTvManager.GetRecordings(new RecordingQuery
- {
- ChannelId = request.ChannelId,
- UserId = request.UserId,
- StartIndex = request.StartIndex,
- Limit = request.Limit,
- Status = request.Status,
- SeriesTimerId = request.SeriesTimerId,
- IsInProgress = request.IsInProgress,
- EnableTotalRecordCount = request.EnableTotalRecordCount,
- IsMovie = request.IsMovie,
- IsNews = request.IsNews,
- IsSeries = request.IsSeries,
- IsKids = request.IsKids,
- IsSports = request.IsSports,
- IsLibraryItem = request.IsLibraryItem,
- Fields = request.GetItemFields(),
- ImageTypeLimit = request.ImageTypeLimit,
- EnableImages = request.EnableImages
-
- }, options);
-
- return ToOptimizedResult(result);
- }
-
- public object Get(GetRecordingSeries request)
- {
- return ToOptimizedResult(new QueryResult<BaseItemDto>());
- }
-
- public object Get(GetRecording request)
- {
- var user = _userManager.GetUserById(request.UserId);
-
- var item = string.IsNullOrEmpty(request.Id) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(request.Id);
-
- var dtoOptions = GetDtoOptions(_authContext, request);
-
- var result = _dtoService.GetBaseItemDto(item, dtoOptions, user);
-
- return ToOptimizedResult(result);
- }
-
- public async Task<object> Get(GetTimer request)
- {
- var result = await _liveTvManager.GetTimer(request.Id, CancellationToken.None).ConfigureAwait(false);
-
- return ToOptimizedResult(result);
- }
-
- public async Task<object> Get(GetTimers request)
- {
- var result = await _liveTvManager.GetTimers(new TimerQuery
- {
- ChannelId = request.ChannelId,
- SeriesTimerId = request.SeriesTimerId,
- IsActive = request.IsActive,
- IsScheduled = request.IsScheduled
-
- }, CancellationToken.None).ConfigureAwait(false);
-
- return ToOptimizedResult(result);
- }
-
- public void Delete(DeleteRecording request)
- {
- AssertUserCanManageLiveTv();
-
- _libraryManager.DeleteItem(_libraryManager.GetItemById(request.Id), new DeleteOptions
- {
- DeleteFileLocation = false
- });
- }
-
- public Task Delete(CancelTimer request)
- {
- AssertUserCanManageLiveTv();
-
- return _liveTvManager.CancelTimer(request.Id);
- }
-
- public Task Post(UpdateTimer request)
- {
- AssertUserCanManageLiveTv();
-
- return _liveTvManager.UpdateTimer(request, CancellationToken.None);
- }
-
- public async Task<object> Get(GetSeriesTimers request)
- {
- var result = await _liveTvManager.GetSeriesTimers(new SeriesTimerQuery
- {
- SortOrder = request.SortOrder,
- SortBy = request.SortBy
-
- }, CancellationToken.None).ConfigureAwait(false);
-
- return ToOptimizedResult(result);
- }
-
- public async Task<object> Get(GetSeriesTimer request)
- {
- var result = await _liveTvManager.GetSeriesTimer(request.Id, CancellationToken.None).ConfigureAwait(false);
-
- return ToOptimizedResult(result);
- }
-
- public Task Delete(CancelSeriesTimer request)
- {
- AssertUserCanManageLiveTv();
-
- return _liveTvManager.CancelSeriesTimer(request.Id);
- }
-
- public Task Post(UpdateSeriesTimer request)
- {
- AssertUserCanManageLiveTv();
-
- return _liveTvManager.UpdateSeriesTimer(request, CancellationToken.None);
- }
-
- public async Task<object> Get(GetDefaultTimer request)
- {
- if (string.IsNullOrEmpty(request.ProgramId))
- {
- var result = await _liveTvManager.GetNewTimerDefaults(CancellationToken.None).ConfigureAwait(false);
-
- return ToOptimizedResult(result);
- }
- else
- {
- var result = await _liveTvManager.GetNewTimerDefaults(request.ProgramId, CancellationToken.None).ConfigureAwait(false);
-
- return ToOptimizedResult(result);
- }
- }
-
- public async Task<object> Get(GetProgram request)
- {
- var user = request.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(request.UserId);
-
- var result = await _liveTvManager.GetProgram(request.Id, CancellationToken.None, user).ConfigureAwait(false);
-
- return ToOptimizedResult(result);
- }
-
- public Task Post(CreateSeriesTimer request)
- {
- AssertUserCanManageLiveTv();
-
- return _liveTvManager.CreateSeriesTimer(request, CancellationToken.None);
- }
-
- public Task Post(CreateTimer request)
- {
- AssertUserCanManageLiveTv();
-
- return _liveTvManager.CreateTimer(request, CancellationToken.None);
- }
-
- public object Get(GetRecordingGroups request)
- {
- return ToOptimizedResult(new QueryResult<BaseItemDto>());
- }
-
- public object Get(GetRecordingGroup request)
- {
- throw new FileNotFoundException();
- }
-
- public object Get(GetGuideInfo request)
- {
- return ToOptimizedResult(_liveTvManager.GetGuideInfo());
- }
-
- public Task Post(ResetTuner request)
- {
- AssertUserCanManageLiveTv();
-
- return _liveTvManager.ResetTuner(request.Id, CancellationToken.None);
- }
- }
-}
diff --git a/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs b/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs
deleted file mode 100644
index 4c608d9a3..000000000
--- a/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api.LiveTv
-{
- public class ProgressiveFileCopier : IAsyncStreamWriter, IHasHeaders
- {
- private readonly ILogger _logger;
- private readonly string _path;
- private readonly Dictionary<string, string> _outputHeaders;
-
- public bool AllowEndOfFile = true;
-
- private readonly IDirectStreamProvider _directStreamProvider;
- private IStreamHelper _streamHelper;
-
- public ProgressiveFileCopier(IStreamHelper streamHelper, string path, Dictionary<string, string> outputHeaders, ILogger logger)
- {
- _path = path;
- _outputHeaders = outputHeaders;
- _logger = logger;
- _streamHelper = streamHelper;
- }
-
- public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, IStreamHelper streamHelper, Dictionary<string, string> outputHeaders, ILogger logger)
- {
- _directStreamProvider = directStreamProvider;
- _outputHeaders = outputHeaders;
- _logger = logger;
- _streamHelper = streamHelper;
- }
-
- public IDictionary<string, string> Headers => _outputHeaders;
-
- public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken)
- {
- if (_directStreamProvider != null)
- {
- await _directStreamProvider.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false);
- return;
- }
-
- var fileOptions = FileOptions.SequentialScan;
-
- // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
- if (Environment.OSVersion.Platform != PlatformID.Win32NT)
- {
- fileOptions |= FileOptions.Asynchronous;
- }
-
- using (var inputStream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, fileOptions))
- {
- var emptyReadLimit = AllowEndOfFile ? 20 : 100;
- var eofCount = 0;
- while (eofCount < emptyReadLimit)
- {
- int bytesRead;
- bytesRead = await _streamHelper.CopyToAsync(inputStream, outputStream, cancellationToken).ConfigureAwait(false);
-
- if (bytesRead == 0)
- {
- eofCount++;
- await Task.Delay(100, cancellationToken).ConfigureAwait(false);
- }
- else
- {
- eofCount = 0;
- }
- }
- }
- }
- }
-}
diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj
index cd329c94f..d703bdb05 100644
--- a/MediaBrowser.Api/MediaBrowser.Api.csproj
+++ b/MediaBrowser.Api/MediaBrowser.Api.csproj
@@ -14,10 +14,6 @@
<Compile Include="..\SharedVersion.cs" />
</ItemGroup>
- <ItemGroup>
- <Folder Include="Library" />
- </ItemGroup>
-
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs
deleted file mode 100644
index 2d61299c7..000000000
--- a/MediaBrowser.Api/Movies/MoviesService.cs
+++ /dev/null
@@ -1,418 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using Jellyfin.Data.Entities;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider;
-using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
-
-namespace MediaBrowser.Api.Movies
-{
- [Route("/Movies/Recommendations", "GET", Summary = "Gets movie recommendations")]
- public class GetMovieRecommendations : IReturn<RecommendationDto[]>, IHasDtoOptions
- {
- [ApiMember(Name = "CategoryLimit", Description = "The max number of categories to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int CategoryLimit { get; set; }
-
- [ApiMember(Name = "ItemLimit", Description = "The max number of items to return per category", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int ItemLimit { get; set; }
-
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public Guid UserId { get; set; }
-
- /// <summary>
- /// Specify this to localize the search to a specific item or folder. Omit to use the root.
- /// </summary>
- /// <value>The parent id.</value>
- [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string ParentId { get; set; }
-
- [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? EnableImages { get; set; }
-
- [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? EnableUserData { get; set; }
-
- [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? ImageTypeLimit { get; set; }
-
- [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string EnableImageTypes { get; set; }
-
- public GetMovieRecommendations()
- {
- CategoryLimit = 5;
- ItemLimit = 8;
- }
-
- public string Fields { get; set; }
- }
-
- /// <summary>
- /// Class MoviesService
- /// </summary>
- [Authenticated]
- public class MoviesService : BaseApiService
- {
- /// <summary>
- /// The _user manager
- /// </summary>
- private readonly IUserManager _userManager;
-
- private readonly ILibraryManager _libraryManager;
-
- private readonly IDtoService _dtoService;
- private readonly IAuthorizationContext _authContext;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="MoviesService" /> class.
- /// </summary>
- public MoviesService(
- ILogger<MoviesService> logger,
- IServerConfigurationManager serverConfigurationManager,
- IHttpResultFactory httpResultFactory,
- IUserManager userManager,
- ILibraryManager libraryManager,
- IDtoService dtoService,
- IAuthorizationContext authContext)
- : base(logger, serverConfigurationManager, httpResultFactory)
- {
- _userManager = userManager;
- _libraryManager = libraryManager;
- _dtoService = dtoService;
- _authContext = authContext;
- }
-
- public object Get(GetMovieRecommendations request)
- {
- var user = _userManager.GetUserById(request.UserId);
-
- var dtoOptions = GetDtoOptions(_authContext, request);
-
- var result = GetRecommendationCategories(user, request.ParentId, request.CategoryLimit, request.ItemLimit, dtoOptions);
-
- return ToOptimizedResult(result);
- }
-
- public QueryResult<BaseItemDto> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request)
- {
- var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
-
- var item = string.IsNullOrEmpty(request.Id) ?
- (!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() :
- _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
-
- var itemTypes = new List<string> { typeof(Movie).Name };
- if (ServerConfigurationManager.Configuration.EnableExternalContentInSuggestions)
- {
- itemTypes.Add(typeof(Trailer).Name);
- itemTypes.Add(typeof(LiveTvProgram).Name);
- }
-
- var dtoOptions = GetDtoOptions(_authContext, request);
-
- var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
- {
- Limit = request.Limit,
- IncludeItemTypes = itemTypes.ToArray(),
- IsMovie = true,
- SimilarTo = item,
- EnableGroupByMetadataKey = true,
- DtoOptions = dtoOptions
-
- });
-
- var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user);
-
- var result = new QueryResult<BaseItemDto>
- {
- Items = returnList,
-
- TotalRecordCount = itemsResult.Count
- };
-
- return result;
- }
-
- private IEnumerable<RecommendationDto> GetRecommendationCategories(User user, string parentId, int categoryLimit, int itemLimit, DtoOptions dtoOptions)
- {
- var categories = new List<RecommendationDto>();
-
- var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? Guid.Empty : new Guid(parentId);
-
- var query = new InternalItemsQuery(user)
- {
- IncludeItemTypes = new[]
- {
- typeof(Movie).Name,
- //typeof(Trailer).Name,
- //typeof(LiveTvProgram).Name
- },
- // IsMovie = true
- OrderBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.Random }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(),
- Limit = 7,
- ParentId = parentIdGuid,
- Recursive = true,
- IsPlayed = true,
- DtoOptions = dtoOptions
- };
-
- var recentlyPlayedMovies = _libraryManager.GetItemList(query);
-
- var itemTypes = new List<string> { typeof(Movie).Name };
- if (ServerConfigurationManager.Configuration.EnableExternalContentInSuggestions)
- {
- itemTypes.Add(typeof(Trailer).Name);
- itemTypes.Add(typeof(LiveTvProgram).Name);
- }
-
- var likedMovies = _libraryManager.GetItemList(new InternalItemsQuery(user)
- {
- IncludeItemTypes = itemTypes.ToArray(),
- IsMovie = true,
- OrderBy = new[] { ItemSortBy.Random }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(),
- Limit = 10,
- IsFavoriteOrLiked = true,
- ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id).ToArray(),
- EnableGroupByMetadataKey = true,
- ParentId = parentIdGuid,
- Recursive = true,
- DtoOptions = dtoOptions
-
- });
-
- var mostRecentMovies = recentlyPlayedMovies.Take(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 categories.OrderBy(i => i.RecommendationType);
- }
-
- private IEnumerable<RecommendationDto> GetWithDirector(
- User user,
- IEnumerable<string> names,
- int itemLimit,
- DtoOptions dtoOptions,
- RecommendationType type)
- {
- var itemTypes = new List<string> { typeof(Movie).Name };
- if (ServerConfigurationManager.Configuration.EnableExternalContentInSuggestions)
- {
- itemTypes.Add(typeof(Trailer).Name);
- itemTypes.Add(typeof(LiveTvProgram).Name);
- }
-
- 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
-
- }).GroupBy(i => i.GetProviderId(MetadataProvider.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture))
- .Select(x => x.First())
- .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<string> { typeof(Movie).Name };
- if (ServerConfigurationManager.Configuration.EnableExternalContentInSuggestions)
- {
- itemTypes.Add(typeof(Trailer).Name);
- itemTypes.Add(typeof(LiveTvProgram).Name);
- }
-
- 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
-
- }).GroupBy(i => i.GetProviderId(MetadataProvider.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture))
- .Select(x => x.First())
- .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, List<BaseItem> baselineItems, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
- {
- var itemTypes = new List<string> { typeof(Movie).Name };
- if (ServerConfigurationManager.Configuration.EnableExternalContentInSuggestions)
- {
- itemTypes.Add(typeof(Trailer).Name);
- itemTypes.Add(typeof(LiveTvProgram).Name);
- }
-
- foreach (var item in baselineItems)
- {
- var similar = _libraryManager.GetItemList(new InternalItemsQuery(user)
- {
- Limit = itemLimit,
- IncludeItemTypes = itemTypes.ToArray(),
- IsMovie = true,
- SimilarTo = item,
- 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(List<BaseItem> items)
- {
- var people = _libraryManager.GetPeople(new InternalPeopleQuery
- {
- ExcludePersonTypes = 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(List<BaseItem> items)
- {
- var people = _libraryManager.GetPeople(new InternalPeopleQuery
- {
- PersonTypes = new[]
- {
- PersonType.Director
- }
- });
-
- var itemIds = items.Select(i => i.Id).ToList();
-
- return people
- .Where(i => itemIds.Contains(i.ItemId))
- .Select(i => i.Name)
- .DistinctNames();
- }
- }
-}
diff --git a/MediaBrowser.Api/Movies/TrailersService.cs b/MediaBrowser.Api/Movies/TrailersService.cs
deleted file mode 100644
index 0b5334235..000000000
--- a/MediaBrowser.Api/Movies/TrailersService.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-using MediaBrowser.Api.UserLibrary;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api.Movies
-{
- [Route("/Trailers", "GET", Summary = "Finds movies and trailers similar to a given trailer.")]
- public class Getrailers : BaseItemsRequest, IReturn<QueryResult<BaseItemDto>>
- {
- }
-
- /// <summary>
- /// Class TrailersService
- /// </summary>
- [Authenticated]
- public class TrailersService : BaseApiService
- {
- /// <summary>
- /// The _user manager
- /// </summary>
- private readonly IUserManager _userManager;
-
- /// <summary>
- /// The _library manager
- /// </summary>
- private readonly ILibraryManager _libraryManager;
-
- /// <summary>
- /// The logger for the created <see cref="ItemsService"/> instances.
- /// </summary>
- private readonly ILogger<ItemsService> _logger;
-
- private readonly IDtoService _dtoService;
- private readonly ILocalizationManager _localizationManager;
- private readonly IJsonSerializer _json;
- private readonly IAuthorizationContext _authContext;
-
- public TrailersService(
- ILoggerFactory loggerFactory,
- IServerConfigurationManager serverConfigurationManager,
- IHttpResultFactory httpResultFactory,
- IUserManager userManager,
- ILibraryManager libraryManager,
- IDtoService dtoService,
- ILocalizationManager localizationManager,
- IJsonSerializer json,
- IAuthorizationContext authContext)
- : base(loggerFactory.CreateLogger<TrailersService>(), serverConfigurationManager, httpResultFactory)
- {
- _userManager = userManager;
- _libraryManager = libraryManager;
- _dtoService = dtoService;
- _localizationManager = localizationManager;
- _json = json;
- _authContext = authContext;
- _logger = loggerFactory.CreateLogger<ItemsService>();
- }
-
- public object Get(Getrailers request)
- {
- var json = _json.SerializeToString(request);
- var getItems = _json.DeserializeFromString<GetItems>(json);
-
- getItems.IncludeItemTypes = "Trailer";
-
- return new ItemsService(
- _logger,
- ServerConfigurationManager,
- ResultFactory,
- _userManager,
- _libraryManager,
- _localizationManager,
- _dtoService,
- _authContext)
- {
- Request = Request,
-
- }.Get(getItems);
- }
- }
-}
diff --git a/MediaBrowser.Api/Music/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs
deleted file mode 100644
index 7d10c9427..000000000
--- a/MediaBrowser.Api/Music/InstantMixService.cs
+++ /dev/null
@@ -1,197 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using Jellyfin.Data.Entities;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Playlists;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api.Music
-{
- [Route("/Songs/{Id}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given song")]
- public class GetInstantMixFromSong : BaseGetSimilarItemsFromItem
- {
- }
-
- [Route("/Albums/{Id}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given album")]
- public class GetInstantMixFromAlbum : BaseGetSimilarItemsFromItem
- {
- }
-
- [Route("/Playlists/{Id}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given playlist")]
- public class GetInstantMixFromPlaylist : BaseGetSimilarItemsFromItem
- {
- }
-
- [Route("/MusicGenres/{Name}/InstantMix", "GET", Summary = "Creates an instant playlist based on a music genre")]
- public class GetInstantMixFromMusicGenre : BaseGetSimilarItems
- {
- [ApiMember(Name = "Name", Description = "The genre name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Name { get; set; }
- }
-
- [Route("/Artists/InstantMix", "GET", Summary = "Creates an instant playlist based on a given artist")]
- public class GetInstantMixFromArtistId : BaseGetSimilarItems
- {
- [ApiMember(Name = "Id", Description = "The artist Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string Id { get; set; }
- }
-
- [Route("/MusicGenres/InstantMix", "GET", Summary = "Creates an instant playlist based on a music genre")]
- public class GetInstantMixFromMusicGenreId : BaseGetSimilarItems
- {
- [ApiMember(Name = "Id", Description = "The genre Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string Id { get; set; }
- }
-
- [Route("/Items/{Id}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given item")]
- public class GetInstantMixFromItem : BaseGetSimilarItemsFromItem
- {
- }
-
- [Authenticated]
- public class InstantMixService : BaseApiService
- {
- private readonly IUserManager _userManager;
-
- private readonly IDtoService _dtoService;
- private readonly ILibraryManager _libraryManager;
- private readonly IMusicManager _musicManager;
- private readonly IAuthorizationContext _authContext;
-
- public InstantMixService(
- ILogger<InstantMixService> logger,
- IServerConfigurationManager serverConfigurationManager,
- IHttpResultFactory httpResultFactory,
- IUserManager userManager,
- IDtoService dtoService,
- IMusicManager musicManager,
- ILibraryManager libraryManager,
- IAuthorizationContext authContext)
- : base(logger, serverConfigurationManager, httpResultFactory)
- {
- _userManager = userManager;
- _dtoService = dtoService;
- _musicManager = musicManager;
- _libraryManager = libraryManager;
- _authContext = authContext;
- }
-
- public object Get(GetInstantMixFromItem request)
- {
- var item = _libraryManager.GetItemById(request.Id);
-
- var user = _userManager.GetUserById(request.UserId);
-
- var dtoOptions = GetDtoOptions(_authContext, request);
-
- var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
-
- return GetResult(items, user, request, dtoOptions);
- }
-
- public object Get(GetInstantMixFromArtistId request)
- {
- var item = _libraryManager.GetItemById(request.Id);
-
- var user = _userManager.GetUserById(request.UserId);
-
- var dtoOptions = GetDtoOptions(_authContext, request);
-
- var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
-
- return GetResult(items, user, request, dtoOptions);
- }
-
- public object Get(GetInstantMixFromMusicGenreId request)
- {
- var item = _libraryManager.GetItemById(request.Id);
-
- var user = _userManager.GetUserById(request.UserId);
-
- var dtoOptions = GetDtoOptions(_authContext, request);
-
- var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
-
- return GetResult(items, user, request, dtoOptions);
- }
-
- public object Get(GetInstantMixFromSong request)
- {
- var item = _libraryManager.GetItemById(request.Id);
-
- var user = _userManager.GetUserById(request.UserId);
-
- var dtoOptions = GetDtoOptions(_authContext, request);
-
- var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
-
- return GetResult(items, user, request, dtoOptions);
- }
-
- public object Get(GetInstantMixFromAlbum request)
- {
- var album = _libraryManager.GetItemById(request.Id);
-
- var user = _userManager.GetUserById(request.UserId);
-
- var dtoOptions = GetDtoOptions(_authContext, request);
-
- var items = _musicManager.GetInstantMixFromItem(album, user, dtoOptions);
-
- return GetResult(items, user, request, dtoOptions);
- }
-
- public object Get(GetInstantMixFromPlaylist request)
- {
- var playlist = (Playlist)_libraryManager.GetItemById(request.Id);
-
- var user = _userManager.GetUserById(request.UserId);
-
- var dtoOptions = GetDtoOptions(_authContext, request);
-
- var items = _musicManager.GetInstantMixFromItem(playlist, user, dtoOptions);
-
- return GetResult(items, user, request, dtoOptions);
- }
-
- public object Get(GetInstantMixFromMusicGenre request)
- {
- var user = _userManager.GetUserById(request.UserId);
-
- var dtoOptions = GetDtoOptions(_authContext, request);
-
- var items = _musicManager.GetInstantMixFromGenres(new[] { request.Name }, user, dtoOptions);
-
- return GetResult(items, user, request, dtoOptions);
- }
-
- private object GetResult(List<BaseItem> items, User user, BaseGetSimilarItems request, DtoOptions dtoOptions)
- {
- var list = items;
-
- var result = new QueryResult<BaseItemDto>
- {
- TotalRecordCount = list.Count
- };
-
- if (request.Limit.HasValue)
- {
- list = list.Take(request.Limit.Value).ToList();
- }
-
- var returnList = _dtoService.GetBaseItemDtos(list, dtoOptions, user);
-
- result.Items = returnList;
-
- return result;
- }
-
- }
-}
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 957e4ae91..84ed5dcac 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -28,7 +28,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.Playback
{
/// <summary>
- /// Class BaseStreamingService
+ /// Class BaseStreamingService.
/// </summary>
public abstract class BaseStreamingService : BaseApiService
{
@@ -216,7 +216,7 @@ namespace MediaBrowser.Api.Playback
UseShellExecute = false,
// Must consume both stdout and stderr or deadlocks may occur
- //RedirectStandardOutput = true,
+ // RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
@@ -303,6 +303,7 @@ namespace MediaBrowser.Api.Playback
{
StartThrottler(state, transcodingJob);
}
+
Logger.LogDebug("StartFfMpeg() finished successfully");
return transcodingJob;
@@ -608,6 +609,7 @@ namespace MediaBrowser.Api.Playback
{
throw new ArgumentException("Invalid timeseek header");
}
+
int index = value.IndexOf('-');
value = index == -1
? value.Substring(Npt.Length)
@@ -639,8 +641,10 @@ namespace MediaBrowser.Api.Playback
{
throw new ArgumentException("Invalid timeseek header");
}
+
timeFactor /= 60;
}
+
return TimeSpan.FromSeconds(secondsSum).Ticks;
}
@@ -685,7 +689,7 @@ namespace MediaBrowser.Api.Playback
state.User = UserManager.GetUserById(auth.UserId);
}
- //if ((Request.UserAgent ?? string.Empty).IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 ||
+ // if ((Request.UserAgent ?? string.Empty).IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 ||
// (Request.UserAgent ?? string.Empty).IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 ||
// (Request.UserAgent ?? string.Empty).IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1)
//{
@@ -716,9 +720,9 @@ namespace MediaBrowser.Api.Playback
state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
- //var primaryImage = item.GetImageInfo(ImageType.Primary, 0) ??
+ // var primaryImage = item.GetImageInfo(ImageType.Primary, 0) ??
// item.Parents.Select(i => i.GetImageInfo(ImageType.Primary, 0)).FirstOrDefault(i => i != null);
- //if (primaryImage != null)
+ // if (primaryImage != null)
//{
// state.AlbumCoverPath = primaryImage.Path;
//}
@@ -885,7 +889,7 @@ namespace MediaBrowser.Api.Playback
if (transcodingProfile != null)
{
state.EstimateContentLength = transcodingProfile.EstimateContentLength;
- //state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
+ // state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
if (state.VideoRequest != null)
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index 627421aac..418cd92b3 100644
--- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
@@ -20,7 +20,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.Playback.Hls
{
/// <summary>
- /// Class BaseHlsService
+ /// Class BaseHlsService.
/// </summary>
public abstract class BaseHlsService : BaseStreamingService
{
@@ -146,6 +146,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
ApiEntryPoint.Instance.OnTranscodeEndRequest(job);
}
+
return ResultFactory.GetResult(GetLivePlaylistText(playlist, state.SegmentLength), MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
}
@@ -178,7 +179,7 @@ namespace MediaBrowser.Api.Playback.Hls
var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(CultureInfo.InvariantCulture);
text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength - 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
- //text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
+ // text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
return text;
}
diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
index 0a6ed2786..fe5f980b1 100644
--- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
@@ -234,6 +234,7 @@ namespace MediaBrowser.Api.Playback.Hls
Logger.LogDebug("Starting transcoding because segmentGap is {0} and max allowed gap is {1}. requestedIndex={2}", requestedIndex - currentTranscodingIndex.Value, segmentGapRequiringTranscodingChange, requestedIndex);
startTranscoding = true;
}
+
if (startTranscoding)
{
// If the playlist doesn't already exist, startup ffmpeg
@@ -257,7 +258,7 @@ namespace MediaBrowser.Api.Playback.Hls
throw;
}
- //await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false);
+ // await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false);
}
else
{
@@ -277,8 +278,8 @@ namespace MediaBrowser.Api.Playback.Hls
}
}
- //Logger.LogInformation("waiting for {0}", segmentPath);
- //while (!File.Exists(segmentPath))
+ // Logger.LogInformation("waiting for {0}", segmentPath);
+ // while (!File.Exists(segmentPath))
//{
// await Task.Delay(50, cancellationToken).ConfigureAwait(false);
//}
@@ -518,6 +519,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
Logger.LogDebug("serving {0} as it's on disk and transcoding stopped", segmentPath);
}
+
cancellationToken.ThrowIfCancellationRequested();
}
else
@@ -717,7 +719,7 @@ namespace MediaBrowser.Api.Playback.Hls
// Having problems in android
return false;
- //return state.VideoRequest.VideoBitRate.HasValue;
+ // return state.VideoRequest.VideoBitRate.HasValue;
}
/// <summary>
@@ -972,7 +974,7 @@ namespace MediaBrowser.Api.Playback.Hls
var queryStringIndex = Request.RawUrl.IndexOf('?');
var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
- //if ((Request.UserAgent ?? string.Empty).IndexOf("roku", StringComparison.OrdinalIgnoreCase) != -1)
+ // if ((Request.UserAgent ?? string.Empty).IndexOf("roku", StringComparison.OrdinalIgnoreCase) != -1)
//{
// queryString = string.Empty;
//}
@@ -1100,7 +1102,7 @@ namespace MediaBrowser.Api.Playback.Hls
}
}
- //args += " -flags -global_header";
+ // args += " -flags -global_header";
}
else
{
@@ -1142,7 +1144,7 @@ namespace MediaBrowser.Api.Playback.Hls
args += " " + keyFrameArg + gopArg;
}
- //args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
+ // args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
@@ -1164,7 +1166,7 @@ namespace MediaBrowser.Api.Playback.Hls
args += " -start_at_zero";
}
- //args += " -flags -global_header";
+ // args += " -flags -global_header";
}
if (!string.IsNullOrEmpty(state.OutputVideoSync))
diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
index 87ccde2e0..8a3d00283 100644
--- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
+++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
@@ -13,7 +13,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.Playback.Hls
{
/// <summary>
- /// Class GetHlsAudioSegment
+ /// Class GetHlsAudioSegment.
/// </summary>
// Can't require authentication just yet due to seeing some requests come from Chrome without full query string
//[Authenticated]
@@ -37,7 +37,7 @@ namespace MediaBrowser.Api.Playback.Hls
}
/// <summary>
- /// Class GetHlsVideoSegment
+ /// Class GetHlsVideoSegment.
/// </summary>
[Route("/Videos/{Id}/hls/{PlaylistId}/stream.m3u8", "GET")]
[Authenticated]
@@ -66,7 +66,7 @@ namespace MediaBrowser.Api.Playback.Hls
}
/// <summary>
- /// Class GetHlsVideoSegment
+ /// Class GetHlsVideoSegment.
/// </summary>
// Can't require authentication just yet due to seeing some requests come from Chrome without full query string
//[Authenticated]
diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
index aefb3f019..9562f9953 100644
--- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
@@ -22,7 +22,7 @@ namespace MediaBrowser.Api.Playback.Hls
}
/// <summary>
- /// Class VideoHlsService
+ /// Class VideoHlsService.
/// </summary>
[Authenticated]
public class VideoHlsService : BaseHlsService
diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs
index 2c6534cc0..427d25508 100644
--- a/MediaBrowser.Api/Playback/MediaInfoService.cs
+++ b/MediaBrowser.Api/Playback/MediaInfoService.cs
@@ -28,7 +28,6 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.Playback
{
- [Route("/Items/{Id}/PlaybackInfo", "GET", Summary = "Gets live playback media info for an item")]
public class GetPlaybackInfo : IReturn<PlaybackInfoResponse>
{
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
@@ -38,24 +37,20 @@ namespace MediaBrowser.Api.Playback
public Guid UserId { get; set; }
}
- [Route("/Items/{Id}/PlaybackInfo", "POST", Summary = "Gets live playback media info for an item")]
public class GetPostedPlaybackInfo : PlaybackInfoRequest, IReturn<PlaybackInfoResponse>
{
}
- [Route("/LiveStreams/Open", "POST", Summary = "Opens a media source")]
public class OpenMediaSource : LiveStreamRequest, IReturn<LiveStreamResponse>
{
}
- [Route("/LiveStreams/Close", "POST", Summary = "Closes a media source")]
public class CloseMediaSource : IReturnVoid
{
[ApiMember(Name = "LiveStreamId", Description = "LiveStreamId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string LiveStreamId { get; set; }
}
- [Route("/Playback/BitrateTest", "GET")]
public class GetBitrateTestBytes
{
[ApiMember(Name = "Size", Description = "Size", IsRequired = true, DataType = "int", ParameterType = "query", Verb = "GET")]
@@ -551,10 +546,12 @@ namespace MediaBrowser.Api.Playback
{
mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
}
+
if (!allowAudioStreamCopy)
{
mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
}
+
mediaSource.TranscodingContainer = streamInfo.Container;
mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
}
@@ -646,7 +643,6 @@ namespace MediaBrowser.Api.Playback
}
return 1;
-
}).ThenBy(i =>
{
// Let's assume direct streaming a file is just as desirable as direct playing a remote url
@@ -656,7 +652,6 @@ namespace MediaBrowser.Api.Playback
}
return 1;
-
}).ThenBy(i =>
{
return i.Protocol switch
@@ -672,7 +667,6 @@ namespace MediaBrowser.Api.Playback
}
return 1;
-
}).ThenBy(originalList.IndexOf)
.ToArray();
}
diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
index 34c7986ca..d51787df2 100644
--- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
@@ -15,7 +15,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.Playback.Progressive
{
/// <summary>
- /// Class GetAudioStream
+ /// Class GetAudioStream.
/// </summary>
[Route("/Audio/{Id}/stream.{Container}", "GET", Summary = "Gets an audio stream")]
[Route("/Audio/{Id}/stream", "GET", Summary = "Gets an audio stream")]
@@ -26,7 +26,7 @@ namespace MediaBrowser.Api.Playback.Progressive
}
/// <summary>
- /// Class AudioService
+ /// Class AudioService.
/// </summary>
// TODO: In order to autheneticate this in the future, Dlna playback will require updating
//[Authenticated]
diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
index c7bf055fb..2ebf0e420 100644
--- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
@@ -21,7 +21,7 @@ using Microsoft.Net.Http.Headers;
namespace MediaBrowser.Api.Playback.Progressive
{
/// <summary>
- /// Class BaseProgressiveStreamingService
+ /// Class BaseProgressiveStreamingService.
/// </summary>
public abstract class BaseProgressiveStreamingService : BaseStreamingService
{
@@ -88,14 +88,17 @@ namespace MediaBrowser.Api.Playback.Progressive
{
return ".ts";
}
+
if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase))
{
return ".ogv";
}
+
if (string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase))
{
return ".webm";
}
+
if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase))
{
return ".asf";
@@ -111,14 +114,17 @@ namespace MediaBrowser.Api.Playback.Progressive
{
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";
@@ -231,7 +237,7 @@ namespace MediaBrowser.Api.Playback.Progressive
}
//// Not static but transcode cache file exists
- //if (isTranscodeCached && state.VideoRequest == null)
+ // if (isTranscodeCached && state.VideoRequest == null)
//{
// var contentType = state.GetMimeType(outputPath);
diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
index a53b848f9..b70fff128 100644
--- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
+++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
@@ -23,6 +23,7 @@ namespace MediaBrowser.Api.Playback.Progressive
private long _bytesWritten = 0;
public long StartPosition { get; set; }
+
public bool AllowEndOfFile = true;
private readonly IDirectStreamProvider _directStreamProvider;
@@ -96,8 +97,8 @@ namespace MediaBrowser.Api.Playback.Progressive
bytesRead = await CopyToInternalAsyncWithSyncRead(inputStream, outputStream, cancellationToken).ConfigureAwait(false);
}
- //var position = fs.Position;
- //_logger.LogDebug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
+ // var position = fs.Position;
+ // _logger.LogDebug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
if (bytesRead == 0)
{
@@ -105,6 +106,7 @@ namespace MediaBrowser.Api.Playback.Progressive
{
eofCount++;
}
+
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
else
diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
index 4de81655c..c3f6b905c 100644
--- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
@@ -15,7 +15,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.Playback.Progressive
{
/// <summary>
- /// Class GetVideoStream
+ /// Class GetVideoStream.
/// </summary>
[Route("/Videos/{Id}/stream.mpegts", "GET")]
[Route("/Videos/{Id}/stream.ts", "GET")]
@@ -59,11 +59,10 @@ namespace MediaBrowser.Api.Playback.Progressive
[Route("/Videos/{Id}/stream", "HEAD")]
public class GetVideoStream : VideoStreamRequest
{
-
}
/// <summary>
- /// Class VideoService
+ /// Class VideoService.
/// </summary>
// TODO: In order to autheneticate this in the future, Dlna playback will require updating
//[Authenticated]
diff --git a/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs b/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs
index 3b8b29995..7e2e337ad 100644
--- a/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs
+++ b/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs
@@ -8,17 +8,17 @@ using MediaBrowser.Model.Services;
namespace MediaBrowser.Api.Playback
{
/// <summary>
- /// Class StaticRemoteStreamWriter
+ /// Class StaticRemoteStreamWriter.
/// </summary>
public class StaticRemoteStreamWriter : IAsyncStreamWriter, IHasHeaders
{
/// <summary>
- /// The _input stream
+ /// The _input stream.
/// </summary>
private readonly HttpResponseInfo _response;
/// <summary>
- /// The _options
+ /// The _options.
/// </summary>
private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs
index 9ba8eda91..67c334e48 100644
--- a/MediaBrowser.Api/Playback/StreamRequest.cs
+++ b/MediaBrowser.Api/Playback/StreamRequest.cs
@@ -4,7 +4,7 @@ using MediaBrowser.Model.Services;
namespace MediaBrowser.Api.Playback
{
/// <summary>
- /// Class StreamRequest
+ /// Class StreamRequest.
/// </summary>
public class StreamRequest : BaseEncodingJobOptions
{
@@ -12,11 +12,15 @@ namespace MediaBrowser.Api.Playback
public string DeviceProfileId { get; set; }
public string Params { get; set; }
+
public string PlaySessionId { get; set; }
+
public string Tag { get; set; }
+
public string SegmentContainer { get; set; }
public int? SegmentLength { get; set; }
+
public int? MinSegments { get; set; }
}
diff --git a/MediaBrowser.Api/Playback/UniversalAudioService.cs b/MediaBrowser.Api/Playback/UniversalAudioService.cs
index a3b319d44..d5d78cf37 100644
--- a/MediaBrowser.Api/Playback/UniversalAudioService.cs
+++ b/MediaBrowser.Api/Playback/UniversalAudioService.cs
@@ -37,10 +37,13 @@ namespace MediaBrowser.Api.Playback
public string DeviceId { get; set; }
public Guid UserId { get; set; }
+
public string AudioCodec { get; set; }
+
public string Container { get; set; }
public int? MaxAudioChannels { get; set; }
+
public int? TranscodingAudioChannels { get; set; }
public long? MaxStreamingBitrate { get; set; }
@@ -49,12 +52,17 @@ namespace MediaBrowser.Api.Playback
public long? StartTimeTicks { get; set; }
public string TranscodingContainer { get; set; }
+
public string TranscodingProtocol { get; set; }
+
public int? MaxAudioSampleRate { get; set; }
+
public int? MaxAudioBitDepth { get; set; }
public bool EnableRedirection { get; set; }
+
public bool EnableRemoteMedia { get; set; }
+
public bool BreakOnNonKeyFrames { get; set; }
public BaseUniversalRequest()
@@ -114,16 +122,27 @@ namespace MediaBrowser.Api.Playback
}
protected IHttpClient HttpClient { get; private set; }
+
protected IUserManager UserManager { get; private set; }
+
protected ILibraryManager LibraryManager { get; private set; }
+
protected IIsoManager IsoManager { get; private set; }
+
protected IMediaEncoder MediaEncoder { get; private set; }
+
protected IFileSystem FileSystem { get; private set; }
+
protected IDlnaManager DlnaManager { get; private set; }
+
protected IDeviceManager DeviceManager { get; private set; }
+
protected IMediaSourceManager MediaSourceManager { get; private set; }
+
protected IJsonSerializer JsonSerializer { get; private set; }
+
protected IAuthorizationContext AuthorizationContext { get; private set; }
+
protected INetworkManager NetworkManager { get; private set; }
public Task<object> Get(GetUniversalAudioStream request)
@@ -259,7 +278,6 @@ namespace MediaBrowser.Api.Playback
UserId = request.UserId,
DeviceProfile = deviceProfile,
MediaSourceId = request.MediaSourceId
-
}).ConfigureAwait(false);
var mediaSource = playbackInfoResult.MediaSources[0];
@@ -329,6 +347,7 @@ namespace MediaBrowser.Api.Playback
{
return await service.Head(newRequest).ConfigureAwait(false);
}
+
return await service.Get(newRequest).ConfigureAwait(false);
}
else
diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs
deleted file mode 100644
index e08a8482e..000000000
--- a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs
+++ /dev/null
@@ -1,234 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Services;
-using MediaBrowser.Model.Tasks;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api.ScheduledTasks
-{
- /// <summary>
- /// Class GetScheduledTask
- /// </summary>
- [Route("/ScheduledTasks/{Id}", "GET", Summary = "Gets a scheduled task, by Id")]
- public class GetScheduledTask : IReturn<TaskInfo>
- {
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
- }
-
- /// <summary>
- /// Class GetScheduledTasks
- /// </summary>
- [Route("/ScheduledTasks", "GET", Summary = "Gets scheduled tasks")]
- public class GetScheduledTasks : IReturn<TaskInfo[]>
- {
- [ApiMember(Name = "IsHidden", Description = "Optional filter tasks that are hidden, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? IsHidden { get; set; }
-
- [ApiMember(Name = "IsEnabled", Description = "Optional filter tasks that are enabled, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? IsEnabled { get; set; }
- }
-
- /// <summary>
- /// Class StartScheduledTask
- /// </summary>
- [Route("/ScheduledTasks/Running/{Id}", "POST", Summary = "Starts a scheduled task")]
- public class StartScheduledTask : IReturnVoid
- {
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string Id { get; set; }
- }
-
- /// <summary>
- /// Class StopScheduledTask
- /// </summary>
- [Route("/ScheduledTasks/Running/{Id}", "DELETE", Summary = "Stops a scheduled task")]
- public class StopScheduledTask : IReturnVoid
- {
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public string Id { get; set; }
- }
-
- /// <summary>
- /// Class UpdateScheduledTaskTriggers
- /// </summary>
- [Route("/ScheduledTasks/{Id}/Triggers", "POST", Summary = "Updates the triggers for a scheduled task")]
- public class UpdateScheduledTaskTriggers : List<TaskTriggerInfo>, IReturnVoid
- {
- /// <summary>
- /// Gets or sets the task id.
- /// </summary>
- /// <value>The task id.</value>
- [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string Id { get; set; }
- }
-
- /// <summary>
- /// Class ScheduledTasksService
- /// </summary>
- [Authenticated(Roles = "Admin")]
- public class ScheduledTaskService : BaseApiService
- {
- /// <summary>
- /// The task manager.
- /// </summary>
- private readonly ITaskManager _taskManager;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="ScheduledTaskService" /> class.
- /// </summary>
- /// <param name="taskManager">The task manager.</param>
- /// <exception cref="ArgumentNullException">taskManager</exception>
- public ScheduledTaskService(
- ILogger<ScheduledTaskService> logger,
- IServerConfigurationManager serverConfigurationManager,
- IHttpResultFactory httpResultFactory,
- ITaskManager taskManager)
- : base(logger, serverConfigurationManager, httpResultFactory)
- {
- _taskManager = taskManager;
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>IEnumerable{TaskInfo}.</returns>
- public object Get(GetScheduledTasks request)
- {
- IEnumerable<IScheduledTaskWorker> result = _taskManager.ScheduledTasks
- .OrderBy(i => i.Name);
-
- if (request.IsHidden.HasValue)
- {
- var val = request.IsHidden.Value;
-
- result = result.Where(i =>
- {
- var isHidden = false;
-
- if (i.ScheduledTask is IConfigurableScheduledTask configurableTask)
- {
- isHidden = configurableTask.IsHidden;
- }
-
- return isHidden == val;
- });
- }
-
- if (request.IsEnabled.HasValue)
- {
- var val = request.IsEnabled.Value;
-
- result = result.Where(i =>
- {
- var isEnabled = true;
-
- if (i.ScheduledTask is IConfigurableScheduledTask configurableTask)
- {
- isEnabled = configurableTask.IsEnabled;
- }
-
- return isEnabled == val;
- });
- }
-
- var infos = result
- .Select(ScheduledTaskHelpers.GetTaskInfo)
- .ToArray();
-
- return ToOptimizedResult(infos);
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>IEnumerable{TaskInfo}.</returns>
- /// <exception cref="ResourceNotFoundException">Task not found</exception>
- public object Get(GetScheduledTask request)
- {
- var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, request.Id));
-
- if (task == null)
- {
- throw new ResourceNotFoundException("Task not found");
- }
-
- var result = ScheduledTaskHelpers.GetTaskInfo(task);
-
- return ToOptimizedResult(result);
- }
-
- /// <summary>
- /// Posts the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <exception cref="ResourceNotFoundException">Task not found</exception>
- public void Post(StartScheduledTask request)
- {
- var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, request.Id));
-
- if (task == null)
- {
- throw new ResourceNotFoundException("Task not found");
- }
-
- _taskManager.Execute(task, new TaskOptions());
- }
-
- /// <summary>
- /// Posts the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <exception cref="ResourceNotFoundException">Task not found</exception>
- public void Delete(StopScheduledTask request)
- {
- var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, request.Id));
-
- if (task == null)
- {
- throw new ResourceNotFoundException("Task not found");
- }
-
- _taskManager.Cancel(task);
- }
-
- /// <summary>
- /// Posts the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <exception cref="ResourceNotFoundException">Task not found</exception>
- public void Post(UpdateScheduledTaskTriggers request)
- {
- // We need to parse this manually because we told service stack not to with IRequiresRequestStream
- // https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs
- var id = GetPathValue(1).ToString();
-
- var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.Ordinal));
-
- if (task == null)
- {
- throw new ResourceNotFoundException("Task not found");
- }
-
- task.Triggers = request.ToArray();
- }
- }
-}
diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs
index 14b9b3618..25dd39f2d 100644
--- a/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs
+++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs
@@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.ScheduledTasks
{
/// <summary>
- /// Class ScheduledTasksWebSocketListener
+ /// Class ScheduledTasksWebSocketListener.
/// </summary>
public class ScheduledTasksWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<TaskInfo>, WebSocketListenerState>
{
diff --git a/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs
index 175984575..2400d6def 100644
--- a/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs
+++ b/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs
@@ -8,7 +8,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.Sessions
{
/// <summary>
- /// Class SessionInfoWebSocketListener
+ /// Class SessionInfoWebSocketListener.
/// </summary>
public class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SessionInfo>, WebSocketListenerState>
{
@@ -19,7 +19,7 @@ namespace MediaBrowser.Api.Sessions
protected override string Name => "Sessions";
/// <summary>
- /// The _kernel
+ /// The _kernel.
/// </summary>
private readonly ISessionManager _sessionManager;
diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs
deleted file mode 100644
index dcd22280a..000000000
--- a/MediaBrowser.Api/SimilarItemsHelper.cs
+++ /dev/null
@@ -1,223 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api
-{
- /// <summary>
- /// Class BaseGetSimilarItemsFromItem
- /// </summary>
- public class BaseGetSimilarItemsFromItem : BaseGetSimilarItems
- {
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
-
- public string ExcludeArtistIds { get; set; }
- }
-
- public class BaseGetSimilarItems : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions
- {
- [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? EnableImages { get; set; }
-
- [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? EnableUserData { get; set; }
-
- [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? ImageTypeLimit { get; set; }
-
- [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string EnableImageTypes { get; set; }
-
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public Guid UserId { get; set; }
-
- /// <summary>
- /// The maximum number of items to return
- /// </summary>
- /// <value>The limit.</value>
- [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? Limit { get; set; }
-
- /// <summary>
- /// Fields to return within the items, in addition to basic information
- /// </summary>
- /// <value>The fields.</value>
- [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string Fields { get; set; }
- }
-
- /// <summary>
- /// Class SimilarItemsHelper
- /// </summary>
- public static class SimilarItemsHelper
- {
- internal static QueryResult<BaseItemDto> GetSimilarItemsResult(DtoOptions dtoOptions, IUserManager userManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, BaseGetSimilarItemsFromItem request, Type[] includeTypes, Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore)
- {
- var user = !request.UserId.Equals(Guid.Empty) ? userManager.GetUserById(request.UserId) : null;
-
- var item = string.IsNullOrEmpty(request.Id) ?
- (!request.UserId.Equals(Guid.Empty) ? libraryManager.GetUserRootFolder() :
- libraryManager.RootFolder) : libraryManager.GetItemById(request.Id);
-
- var query = new InternalItemsQuery(user)
- {
- IncludeItemTypes = includeTypes.Select(i => i.Name).ToArray(),
- Recursive = true,
- DtoOptions = dtoOptions
- };
-
- // ExcludeArtistIds
- if (!string.IsNullOrEmpty(request.ExcludeArtistIds))
- {
- query.ExcludeArtistIds = BaseApiService.GetGuids(request.ExcludeArtistIds);
- }
-
- var inputItems = libraryManager.GetItemList(query);
-
- var items = GetSimilaritems(item, libraryManager, inputItems, getSimilarityScore)
- .ToList();
-
- var returnItems = items;
-
- if (request.Limit.HasValue)
- {
- returnItems = returnItems.Take(request.Limit.Value).ToList();
- }
-
- var dtos = dtoService.GetBaseItemDtos(returnItems, dtoOptions, user);
-
- return new QueryResult<BaseItemDto>
- {
- Items = dtos,
-
- TotalRecordCount = items.Count
- };
- }
-
- /// <summary>
- /// Gets the similaritems.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="libraryManager">The library manager.</param>
- /// <param name="inputItems">The input items.</param>
- /// <param name="getSimilarityScore">The get similarity score.</param>
- /// <returns>IEnumerable{BaseItem}.</returns>
- internal static IEnumerable<BaseItem> GetSimilaritems(BaseItem item, ILibraryManager libraryManager, IEnumerable<BaseItem> inputItems, Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore)
- {
- var itemId = item.Id;
- inputItems = inputItems.Where(i => i.Id != itemId);
- var itemPeople = libraryManager.GetPeople(item);
- var allPeople = libraryManager.GetPeople(new InternalPeopleQuery
- {
- AppearsInItemId = item.Id
- });
-
- return inputItems.Select(i => new Tuple<BaseItem, int>(i, getSimilarityScore(item, itemPeople, allPeople, i)))
- .Where(i => i.Item2 > 2)
- .OrderByDescending(i => i.Item2)
- .Select(i => i.Item1);
- }
-
- private static IEnumerable<string> GetTags(BaseItem item)
- {
- return item.Tags;
- }
-
- /// <summary>
- /// Gets the similiarity score.
- /// </summary>
- /// <param name="item1">The item1.</param>
- /// <param name="item1People">The item1 people.</param>
- /// <param name="allPeople">All people.</param>
- /// <param name="item2">The item2.</param>
- /// <returns>System.Int32.</returns>
- internal static int GetSimiliarityScore(BaseItem item1, List<PersonInfo> item1People, List<PersonInfo> allPeople, BaseItem item2)
- {
- var points = 0;
-
- if (!string.IsNullOrEmpty(item1.OfficialRating) && string.Equals(item1.OfficialRating, item2.OfficialRating, StringComparison.OrdinalIgnoreCase))
- {
- points += 10;
- }
-
- // Find common genres
- points += item1.Genres.Where(i => item2.Genres.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 10);
-
- // Find common tags
- points += GetTags(item1).Where(i => GetTags(item2).Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 10);
-
- // Find common studios
- points += item1.Studios.Where(i => item2.Studios.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 3);
-
- var item2PeopleNames = allPeople.Where(i => i.ItemId == item2.Id)
- .Select(i => i.Name)
- .Where(i => !string.IsNullOrWhiteSpace(i))
- .DistinctNames()
- .ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
-
- points += item1People.Where(i => item2PeopleNames.ContainsKey(i.Name)).Sum(i =>
- {
- if (string.Equals(i.Type, PersonType.Director, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Director, StringComparison.OrdinalIgnoreCase))
- {
- return 5;
- }
- if (string.Equals(i.Type, PersonType.Actor, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Actor, StringComparison.OrdinalIgnoreCase))
- {
- return 3;
- }
- if (string.Equals(i.Type, PersonType.Composer, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Composer, StringComparison.OrdinalIgnoreCase))
- {
- return 3;
- }
- if (string.Equals(i.Type, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase))
- {
- return 3;
- }
- if (string.Equals(i.Type, PersonType.Writer, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Writer, StringComparison.OrdinalIgnoreCase))
- {
- return 2;
- }
-
- return 1;
- });
-
- if (item1.ProductionYear.HasValue && item2.ProductionYear.HasValue)
- {
- var diff = Math.Abs(item1.ProductionYear.Value - item2.ProductionYear.Value);
-
- // Add if they came out within the same decade
- if (diff < 10)
- {
- points += 2;
- }
-
- // And more if within five years
- if (diff < 5)
- {
- points += 2;
- }
- }
-
- return points;
- }
-
- }
-}
diff --git a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs
index 1e14ea552..daa1b521f 100644
--- a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs
+++ b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs
@@ -11,93 +11,66 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.SyncPlay
{
- [Route("/SyncPlay/{SessionId}/NewGroup", "POST", Summary = "Create a new SyncPlay group")]
+ [Route("/SyncPlay/New", "POST", Summary = "Create a new SyncPlay group")]
[Authenticated]
- public class SyncPlayNewGroup : IReturnVoid
+ public class SyncPlayNew : IReturnVoid
{
- [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string SessionId { get; set; }
}
- [Route("/SyncPlay/{SessionId}/JoinGroup", "POST", Summary = "Join an existing SyncPlay group")]
+ [Route("/SyncPlay/Join", "POST", Summary = "Join an existing SyncPlay group")]
[Authenticated]
- public class SyncPlayJoinGroup : IReturnVoid
+ public class SyncPlayJoin : IReturnVoid
{
- [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string SessionId { get; set; }
-
/// <summary>
/// Gets or sets the Group id.
/// </summary>
/// <value>The Group id to join.</value>
[ApiMember(Name = "GroupId", Description = "Group Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
public string GroupId { get; set; }
-
- /// <summary>
- /// Gets or sets the playing item id.
- /// </summary>
- /// <value>The client's currently playing item id.</value>
- [ApiMember(Name = "PlayingItemId", Description = "Client's playing item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string PlayingItemId { get; set; }
}
- [Route("/SyncPlay/{SessionId}/LeaveGroup", "POST", Summary = "Leave joined SyncPlay group")]
+ [Route("/SyncPlay/Leave", "POST", Summary = "Leave joined SyncPlay group")]
[Authenticated]
- public class SyncPlayLeaveGroup : IReturnVoid
+ public class SyncPlayLeave : IReturnVoid
{
- [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string SessionId { get; set; }
}
- [Route("/SyncPlay/{SessionId}/ListGroups", "POST", Summary = "List SyncPlay groups")]
+ [Route("/SyncPlay/List", "GET", Summary = "List SyncPlay groups")]
[Authenticated]
- public class SyncPlayListGroups : IReturnVoid
+ public class SyncPlayList : IReturnVoid
{
- [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string SessionId { get; set; }
-
/// <summary>
/// Gets or sets the filter item id.
/// </summary>
/// <value>The filter item id.</value>
- [ApiMember(Name = "FilterItemId", Description = "Filter by item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
+ [ApiMember(Name = "FilterItemId", Description = "Filter by item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string FilterItemId { get; set; }
}
- [Route("/SyncPlay/{SessionId}/PlayRequest", "POST", Summary = "Request play in SyncPlay group")]
+ [Route("/SyncPlay/Play", "POST", Summary = "Request play in SyncPlay group")]
[Authenticated]
- public class SyncPlayPlayRequest : IReturnVoid
+ public class SyncPlayPlay : IReturnVoid
{
- [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string SessionId { get; set; }
}
- [Route("/SyncPlay/{SessionId}/PauseRequest", "POST", Summary = "Request pause in SyncPlay group")]
+ [Route("/SyncPlay/Pause", "POST", Summary = "Request pause in SyncPlay group")]
[Authenticated]
- public class SyncPlayPauseRequest : IReturnVoid
+ public class SyncPlayPause : IReturnVoid
{
- [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string SessionId { get; set; }
}
- [Route("/SyncPlay/{SessionId}/SeekRequest", "POST", Summary = "Request seek in SyncPlay group")]
+ [Route("/SyncPlay/Seek", "POST", Summary = "Request seek in SyncPlay group")]
[Authenticated]
- public class SyncPlaySeekRequest : IReturnVoid
+ public class SyncPlaySeek : IReturnVoid
{
- [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string SessionId { get; set; }
-
[ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")]
public long PositionTicks { get; set; }
}
- [Route("/SyncPlay/{SessionId}/BufferingRequest", "POST", Summary = "Request group wait in SyncPlay group while buffering")]
+ [Route("/SyncPlay/Buffering", "POST", Summary = "Request group wait in SyncPlay group while buffering")]
[Authenticated]
- public class SyncPlayBufferingRequest : IReturnVoid
+ public class SyncPlayBuffering : IReturnVoid
{
- [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string SessionId { get; set; }
-
/// <summary>
/// Gets or sets the date used to pin PositionTicks in time.
/// </summary>
@@ -109,20 +82,17 @@ namespace MediaBrowser.Api.SyncPlay
public long PositionTicks { get; set; }
/// <summary>
- /// Gets or sets whether this is a buffering or a buffering-done request.
+ /// Gets or sets whether this is a buffering or a ready request.
/// </summary>
/// <value><c>true</c> if buffering is complete; <c>false</c> otherwise.</value>
[ApiMember(Name = "BufferingDone", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")]
public bool BufferingDone { get; set; }
}
- [Route("/SyncPlay/{SessionId}/UpdatePing", "POST", Summary = "Update session ping")]
+ [Route("/SyncPlay/Ping", "POST", Summary = "Update session ping")]
[Authenticated]
- public class SyncPlayUpdatePing : IReturnVoid
+ public class SyncPlayPing : IReturnVoid
{
- [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string SessionId { get; set; }
-
[ApiMember(Name = "Ping", IsRequired = true, DataType = "double", ParameterType = "query", Verb = "POST")]
public double Ping { get; set; }
}
@@ -158,7 +128,7 @@ namespace MediaBrowser.Api.SyncPlay
/// Handles the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(SyncPlayNewGroup request)
+ public void Post(SyncPlayNew request)
{
var currentSession = GetSession(_sessionContext);
_syncPlayManager.NewGroup(currentSession, CancellationToken.None);
@@ -168,30 +138,20 @@ namespace MediaBrowser.Api.SyncPlay
/// Handles the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(SyncPlayJoinGroup request)
+ public void Post(SyncPlayJoin request)
{
var currentSession = GetSession(_sessionContext);
Guid groupId;
- Guid playingItemId = Guid.Empty;
-
if (!Guid.TryParse(request.GroupId, out groupId))
{
Logger.LogError("JoinGroup: {0} is not a valid format for GroupId. Ignoring request.", request.GroupId);
return;
}
- // Both null and empty strings mean that client isn't playing anything
- if (!string.IsNullOrEmpty(request.PlayingItemId) && !Guid.TryParse(request.PlayingItemId, out playingItemId))
- {
- Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId);
- return;
- }
-
var joinRequest = new JoinGroupRequest()
{
- GroupId = groupId,
- PlayingItemId = playingItemId
+ GroupId = groupId
};
_syncPlayManager.JoinGroup(currentSession, groupId, joinRequest, CancellationToken.None);
@@ -201,7 +161,7 @@ namespace MediaBrowser.Api.SyncPlay
/// Handles the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(SyncPlayLeaveGroup request)
+ public void Post(SyncPlayLeave request)
{
var currentSession = GetSession(_sessionContext);
_syncPlayManager.LeaveGroup(currentSession, CancellationToken.None);
@@ -212,7 +172,7 @@ namespace MediaBrowser.Api.SyncPlay
/// </summary>
/// <param name="request">The request.</param>
/// <value>The requested list of groups.</value>
- public List<GroupInfoView> Post(SyncPlayListGroups request)
+ public List<GroupInfoView> Get(SyncPlayList request)
{
var currentSession = GetSession(_sessionContext);
var filterItemId = Guid.Empty;
@@ -229,7 +189,7 @@ namespace MediaBrowser.Api.SyncPlay
/// Handles the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(SyncPlayPlayRequest request)
+ public void Post(SyncPlayPlay request)
{
var currentSession = GetSession(_sessionContext);
var syncPlayRequest = new PlaybackRequest()
@@ -243,7 +203,7 @@ namespace MediaBrowser.Api.SyncPlay
/// Handles the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(SyncPlayPauseRequest request)
+ public void Post(SyncPlayPause request)
{
var currentSession = GetSession(_sessionContext);
var syncPlayRequest = new PlaybackRequest()
@@ -257,7 +217,7 @@ namespace MediaBrowser.Api.SyncPlay
/// Handles the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(SyncPlaySeekRequest request)
+ public void Post(SyncPlaySeek request)
{
var currentSession = GetSession(_sessionContext);
var syncPlayRequest = new PlaybackRequest()
@@ -272,12 +232,12 @@ namespace MediaBrowser.Api.SyncPlay
/// Handles the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(SyncPlayBufferingRequest request)
+ public void Post(SyncPlayBuffering request)
{
var currentSession = GetSession(_sessionContext);
var syncPlayRequest = new PlaybackRequest()
{
- Type = request.BufferingDone ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering,
+ Type = request.BufferingDone ? PlaybackRequestType.Ready : PlaybackRequestType.Buffer,
When = DateTime.Parse(request.When),
PositionTicks = request.PositionTicks
};
@@ -288,12 +248,12 @@ namespace MediaBrowser.Api.SyncPlay
/// Handles the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(SyncPlayUpdatePing request)
+ public void Post(SyncPlayPing request)
{
var currentSession = GetSession(_sessionContext);
var syncPlayRequest = new PlaybackRequest()
{
- Type = PlaybackRequestType.UpdatePing,
+ Type = PlaybackRequestType.Ping,
Ping = Convert.ToInt64(request.Ping)
};
_syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
diff --git a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs
index 8e4860be4..39976371a 100644
--- a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs
+++ b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs
@@ -8,7 +8,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.System
{
/// <summary>
- /// Class SessionInfoWebSocketListener
+ /// Class SessionInfoWebSocketListener.
/// </summary>
public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<ActivityLogEntry[], WebSocketListenerState>
{
@@ -19,7 +19,7 @@ namespace MediaBrowser.Api.System
protected override string Name => "ActivityLogEntry";
/// <summary>
- /// The _kernel
+ /// The _kernel.
/// </summary>
private readonly IActivityManager _activityManager;
diff --git a/MediaBrowser.Api/TranscodingJob.cs b/MediaBrowser.Api/TranscodingJob.cs
index 8c24e3ce1..bfc311a27 100644
--- a/MediaBrowser.Api/TranscodingJob.cs
+++ b/MediaBrowser.Api/TranscodingJob.cs
@@ -32,6 +32,7 @@ namespace MediaBrowser.Api
/// </summary>
/// <value>The path.</value>
public MediaSourceInfo MediaSource { get; set; }
+
public string Path { get; set; }
/// <summary>
/// Gets or sets the type.
@@ -43,6 +44,7 @@ namespace MediaBrowser.Api
/// </summary>
/// <value>The process.</value>
public Process Process { get; set; }
+
public ILogger Logger { get; private set; }
/// <summary>
/// Gets or sets the active request count.
@@ -62,18 +64,23 @@ namespace MediaBrowser.Api
public object ProcessLock = new object();
public bool HasExited { get; set; }
+
public bool IsUserPaused { get; set; }
public string Id { get; set; }
public float? Framerate { get; set; }
+
public double? CompletionPercentage { get; set; }
public long? BytesDownloaded { get; set; }
+
public long? BytesTranscoded { get; set; }
+
public int? BitRate { get; set; }
public long? TranscodingPositionTicks { get; set; }
+
public long? DownloadPositionTicks { get; set; }
public TranscodingThrottler TranscodingThrottler { get; set; }
@@ -81,6 +88,7 @@ namespace MediaBrowser.Api
private readonly object _timerLock = new object();
public DateTime LastPingDate { get; set; }
+
public int PingTimeout { get; set; }
public TranscodingJob(ILogger logger)
diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs
deleted file mode 100644
index bef91d54d..000000000
--- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs
+++ /dev/null
@@ -1,143 +0,0 @@
-using System;
-using System.Collections.Generic;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api.UserLibrary
-{
- /// <summary>
- /// Class GetArtists
- /// </summary>
- [Route("/Artists", "GET", Summary = "Gets all artists from a given item, folder, or the entire library")]
- public class GetArtists : GetItemsByName
- {
- }
-
- [Route("/Artists/AlbumArtists", "GET", Summary = "Gets all album artists from a given item, folder, or the entire library")]
- public class GetAlbumArtists : GetItemsByName
- {
- }
-
- [Route("/Artists/{Name}", "GET", Summary = "Gets an artist, by name")]
- public class GetArtist : IReturn<BaseItemDto>
- {
- /// <summary>
- /// Gets or sets the name.
- /// </summary>
- /// <value>The name.</value>
- [ApiMember(Name = "Name", Description = "The artist name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Name { get; set; }
-
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public Guid UserId { get; set; }
- }
-
- /// <summary>
- /// Class ArtistsService
- /// </summary>
- [Authenticated]
- public class ArtistsService : BaseItemsByNameService<MusicArtist>
- {
- public ArtistsService(
- ILogger<ArtistsService> logger,
- IServerConfigurationManager serverConfigurationManager,
- IHttpResultFactory httpResultFactory,
- IUserManager userManager,
- ILibraryManager libraryManager,
- IUserDataManager userDataRepository,
- IDtoService dtoService,
- IAuthorizationContext authorizationContext)
- : base(
- logger,
- serverConfigurationManager,
- httpResultFactory,
- userManager,
- libraryManager,
- userDataRepository,
- dtoService,
- authorizationContext)
- {
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetArtist request)
- {
- return GetItem(request);
- }
-
- /// <summary>
- /// Gets the item.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>Task{BaseItemDto}.</returns>
- private BaseItemDto GetItem(GetArtist request)
- {
- var dtoOptions = GetDtoOptions(AuthorizationContext, request);
-
- var item = GetArtist(request.Name, LibraryManager, dtoOptions);
-
- if (!request.UserId.Equals(Guid.Empty))
- {
- var user = UserManager.GetUserById(request.UserId);
-
- return DtoService.GetBaseItemDto(item, dtoOptions, user);
- }
-
- return DtoService.GetBaseItemDto(item, dtoOptions);
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetArtists request)
- {
- return GetResultSlim(request);
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetAlbumArtists request)
- {
- var result = GetResultSlim(request);
-
- return ToOptimizedResult(result);
- }
-
- protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
- {
- return request is GetAlbumArtists ? LibraryManager.GetAlbumArtists(query) : LibraryManager.GetArtists(query);
- }
-
- /// <summary>
- /// Gets all items.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="items">The items.</param>
- /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
- protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items)
- {
- throw new NotImplementedException();
- }
- }
-}
diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
deleted file mode 100644
index a1ec08467..000000000
--- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
+++ /dev/null
@@ -1,388 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Jellyfin.Data.Entities;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api.UserLibrary
-{
- /// <summary>
- /// Class BaseItemsByNameService
- /// </summary>
- /// <typeparam name="TItemType">The type of the T item type.</typeparam>
- public abstract class BaseItemsByNameService<TItemType> : BaseApiService
- where TItemType : BaseItem, IItemByName
- {
- /// <summary>
- /// Initializes a new instance of the <see cref="BaseItemsByNameService{TItemType}" /> class.
- /// </summary>
- /// <param name="userManager">The user manager.</param>
- /// <param name="libraryManager">The library manager.</param>
- /// <param name="userDataRepository">The user data repository.</param>
- /// <param name="dtoService">The dto service.</param>
- protected BaseItemsByNameService(
- ILogger<BaseItemsByNameService<TItemType>> logger,
- IServerConfigurationManager serverConfigurationManager,
- IHttpResultFactory httpResultFactory,
- IUserManager userManager,
- ILibraryManager libraryManager,
- IUserDataManager userDataRepository,
- IDtoService dtoService,
- IAuthorizationContext authorizationContext)
- : base(logger, serverConfigurationManager, httpResultFactory)
- {
- UserManager = userManager;
- LibraryManager = libraryManager;
- UserDataRepository = userDataRepository;
- DtoService = dtoService;
- AuthorizationContext = authorizationContext;
- }
-
- /// <summary>
- /// Gets the _user manager.
- /// </summary>
- protected IUserManager UserManager { get; }
-
- /// <summary>
- /// Gets the library manager
- /// </summary>
- protected ILibraryManager LibraryManager { get; }
-
- protected IUserDataManager UserDataRepository { get; }
-
- protected IDtoService DtoService { get; }
-
- protected IAuthorizationContext AuthorizationContext { get; }
-
- protected BaseItem GetParentItem(GetItemsByName request)
- {
- BaseItem parentItem;
-
- if (!request.UserId.Equals(Guid.Empty))
- {
- var user = UserManager.GetUserById(request.UserId);
- parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.GetUserRootFolder() : LibraryManager.GetItemById(request.ParentId);
- }
- else
- {
- parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.RootFolder : LibraryManager.GetItemById(request.ParentId);
- }
-
- return parentItem;
- }
-
- protected string GetParentItemViewType(GetItemsByName request)
- {
- var parent = GetParentItem(request);
-
- if (parent is IHasCollectionType collectionFolder)
- {
- return collectionFolder.CollectionType;
- }
-
- return null;
- }
-
- protected QueryResult<BaseItemDto> GetResultSlim(GetItemsByName request)
- {
- var dtoOptions = GetDtoOptions(AuthorizationContext, request);
-
- User user = null;
- BaseItem parentItem;
-
- if (!request.UserId.Equals(Guid.Empty))
- {
- user = UserManager.GetUserById(request.UserId);
- parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.GetUserRootFolder() : LibraryManager.GetItemById(request.ParentId);
- }
- else
- {
- parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.RootFolder : LibraryManager.GetItemById(request.ParentId);
- }
-
- var excludeItemTypes = request.GetExcludeItemTypes();
- var includeItemTypes = request.GetIncludeItemTypes();
- var mediaTypes = request.GetMediaTypes();
-
- var query = new InternalItemsQuery(user)
- {
- ExcludeItemTypes = excludeItemTypes,
- IncludeItemTypes = includeItemTypes,
- MediaTypes = mediaTypes,
- StartIndex = request.StartIndex,
- Limit = request.Limit,
- IsFavorite = request.IsFavorite,
- NameLessThan = request.NameLessThan,
- NameStartsWith = request.NameStartsWith,
- NameStartsWithOrGreater = request.NameStartsWithOrGreater,
- Tags = request.GetTags(),
- OfficialRatings = request.GetOfficialRatings(),
- Genres = request.GetGenres(),
- GenreIds = GetGuids(request.GenreIds),
- StudioIds = GetGuids(request.StudioIds),
- Person = request.Person,
- PersonIds = GetGuids(request.PersonIds),
- PersonTypes = request.GetPersonTypes(),
- Years = request.GetYears(),
- MinCommunityRating = request.MinCommunityRating,
- DtoOptions = dtoOptions,
- SearchTerm = request.SearchTerm,
- EnableTotalRecordCount = request.EnableTotalRecordCount
- };
-
- if (!string.IsNullOrWhiteSpace(request.ParentId))
- {
- if (parentItem is Folder)
- {
- query.AncestorIds = new[] { new Guid(request.ParentId) };
- }
- else
- {
- query.ItemIds = new[] { new Guid(request.ParentId) };
- }
- }
-
- // Studios
- if (!string.IsNullOrEmpty(request.Studios))
- {
- query.StudioIds = request.Studios.Split('|').Select(i =>
- {
- try
- {
- return LibraryManager.GetStudio(i);
- }
- catch
- {
- return null;
- }
- }).Where(i => i != null).Select(i => i.Id).ToArray();
- }
-
- foreach (var filter in request.GetFilters())
- {
- 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 = GetItems(request, query);
-
- var dtos = result.Items.Select(i =>
- {
- var dto = DtoService.GetItemByNameDto(i.Item1, dtoOptions, null, user);
-
- if (!string.IsNullOrWhiteSpace(request.IncludeItemTypes))
- {
- SetItemCounts(dto, i.Item2);
- }
- return dto;
- });
-
- return new QueryResult<BaseItemDto>
- {
- Items = dtos.ToArray(),
- TotalRecordCount = result.TotalRecordCount
- };
- }
-
- protected virtual QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
- {
- return new QueryResult<(BaseItem, ItemCounts)>();
- }
-
- private void SetItemCounts(BaseItemDto dto, ItemCounts counts)
- {
- 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;
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>Task{ItemsResult}.</returns>
- protected QueryResult<BaseItemDto> GetResult(GetItemsByName request)
- {
- var dtoOptions = GetDtoOptions(AuthorizationContext, request);
-
- User user = null;
- BaseItem parentItem;
-
- if (!request.UserId.Equals(Guid.Empty))
- {
- user = UserManager.GetUserById(request.UserId);
- parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.GetUserRootFolder() : LibraryManager.GetItemById(request.ParentId);
- }
- else
- {
- parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.RootFolder : LibraryManager.GetItemById(request.ParentId);
- }
-
- IList<BaseItem> items;
-
- var excludeItemTypes = request.GetExcludeItemTypes();
- var includeItemTypes = request.GetIncludeItemTypes();
- var mediaTypes = request.GetMediaTypes();
-
- var query = new InternalItemsQuery(user)
- {
- ExcludeItemTypes = excludeItemTypes,
- IncludeItemTypes = includeItemTypes,
- MediaTypes = mediaTypes,
- DtoOptions = dtoOptions
- };
-
- bool Filter(BaseItem i) => FilterItem(request, i, excludeItemTypes, includeItemTypes, mediaTypes);
-
- if (parentItem.IsFolder)
- {
- var folder = (Folder)parentItem;
-
- if (!request.UserId.Equals(Guid.Empty))
- {
- items = request.Recursive ?
- folder.GetRecursiveChildren(user, query).ToList() :
- folder.GetChildren(user, true).Where(Filter).ToList();
- }
- else
- {
- items = request.Recursive ?
- folder.GetRecursiveChildren(Filter) :
- folder.Children.Where(Filter).ToList();
- }
- }
- else
- {
- items = new[] { parentItem }.Where(Filter).ToList();
- }
-
- var extractedItems = GetAllItems(request, items);
-
- var filteredItems = LibraryManager.Sort(extractedItems, user, request.GetOrderBy());
-
- var ibnItemsArray = filteredItems.ToList();
-
- IEnumerable<BaseItem> ibnItems = ibnItemsArray;
-
- var result = new QueryResult<BaseItemDto>
- {
- TotalRecordCount = ibnItemsArray.Count
- };
-
- if (request.StartIndex.HasValue || request.Limit.HasValue)
- {
- if (request.StartIndex.HasValue)
- {
- ibnItems = ibnItems.Skip(request.StartIndex.Value);
- }
-
- if (request.Limit.HasValue)
- {
- ibnItems = ibnItems.Take(request.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));
-
- result.Items = dtos.Where(i => i != null).ToArray();
-
- return result;
- }
-
- /// <summary>
- /// Filters the items.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="f">The f.</param>
- /// <param name="excludeItemTypes">The exclude item types.</param>
- /// <param name="includeItemTypes">The include item types.</param>
- /// <param name="mediaTypes">The media types.</param>
- /// <returns>IEnumerable{BaseItem}.</returns>
- private bool FilterItem(GetItemsByName request, BaseItem f, string[] excludeItemTypes, string[] includeItemTypes, string[] mediaTypes)
- {
- // Exclude item types
- if (excludeItemTypes.Length > 0 && excludeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
- {
- return false;
- }
-
- // Include item types
- if (includeItemTypes.Length > 0 && !includeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
- {
- return false;
- }
-
- // Include MediaTypes
- if (mediaTypes.Length > 0 && !mediaTypes.Contains(f.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
- {
- return false;
- }
-
- return true;
- }
-
- /// <summary>
- /// Gets all items.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="items">The items.</param>
- /// <returns>IEnumerable{Task{`0}}.</returns>
- protected abstract IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items);
- }
-
- /// <summary>
- /// Class GetItemsByName
- /// </summary>
- public class GetItemsByName : BaseItemsRequest, IReturn<QueryResult<BaseItemDto>>
- {
- public GetItemsByName()
- {
- Recursive = true;
- }
- }
-}
diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
deleted file mode 100644
index 7561b5c89..000000000
--- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
+++ /dev/null
@@ -1,475 +0,0 @@
-using System;
-using System.Linq;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Services;
-
-namespace MediaBrowser.Api.UserLibrary
-{
- public abstract class BaseItemsRequest : IHasDtoOptions
- {
- protected BaseItemsRequest()
- {
- EnableImages = true;
- EnableTotalRecordCount = true;
- }
-
- /// <summary>
- /// Gets or sets the max offical rating.
- /// </summary>
- /// <value>The max offical rating.</value>
- [ApiMember(Name = "MaxOfficialRating", Description = "Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string MaxOfficialRating { get; set; }
-
- [ApiMember(Name = "HasThemeSong", Description = "Optional filter by items with theme songs.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public bool? HasThemeSong { get; set; }
-
- [ApiMember(Name = "HasThemeVideo", Description = "Optional filter by items with theme videos.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public bool? HasThemeVideo { get; set; }
-
- [ApiMember(Name = "HasSubtitles", Description = "Optional filter by items with subtitles.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public bool? HasSubtitles { get; set; }
-
- [ApiMember(Name = "HasSpecialFeature", Description = "Optional filter by items with special features.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public bool? HasSpecialFeature { get; set; }
-
- [ApiMember(Name = "HasTrailer", Description = "Optional filter by items with trailers.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public bool? HasTrailer { get; set; }
-
- [ApiMember(Name = "AdjacentTo", Description = "Optional. Return items that are siblings of a supplied item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string AdjacentTo { get; set; }
-
- [ApiMember(Name = "MinIndexNumber", Description = "Optional filter by minimum index number.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? MinIndexNumber { get; set; }
-
- [ApiMember(Name = "ParentIndexNumber", Description = "Optional filter by parent index number.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? ParentIndexNumber { get; set; }
-
- [ApiMember(Name = "HasParentalRating", Description = "Optional filter by items that have or do not have a parental rating", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? HasParentalRating { get; set; }
-
- [ApiMember(Name = "IsHD", Description = "Optional filter by items that are HD or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? IsHD { get; set; }
-
- public bool? Is4K { get; set; }
-
- [ApiMember(Name = "LocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string LocationTypes { get; set; }
-
- [ApiMember(Name = "ExcludeLocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string ExcludeLocationTypes { get; set; }
-
- [ApiMember(Name = "IsMissing", Description = "Optional filter by items that are missing episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? IsMissing { get; set; }
-
- [ApiMember(Name = "IsUnaired", Description = "Optional filter by items that are unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? IsUnaired { get; set; }
-
- [ApiMember(Name = "MinCommunityRating", Description = "Optional filter by minimum community rating.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public double? MinCommunityRating { get; set; }
-
- [ApiMember(Name = "MinCriticRating", Description = "Optional filter by minimum critic rating.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public double? MinCriticRating { get; set; }
-
- [ApiMember(Name = "AiredDuringSeason", Description = "Gets all episodes that aired during a season, including specials.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? AiredDuringSeason { get; set; }
-
- [ApiMember(Name = "MinPremiereDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string MinPremiereDate { get; set; }
-
- [ApiMember(Name = "MinDateLastSaved", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string MinDateLastSaved { get; set; }
-
- [ApiMember(Name = "MinDateLastSavedForUser", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string MinDateLastSavedForUser { get; set; }
-
- [ApiMember(Name = "MaxPremiereDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string MaxPremiereDate { get; set; }
-
- [ApiMember(Name = "HasOverview", Description = "Optional filter by items that have an overview or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? HasOverview { get; set; }
-
- [ApiMember(Name = "HasImdbId", Description = "Optional filter by items that have an imdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? HasImdbId { get; set; }
-
- [ApiMember(Name = "HasTmdbId", Description = "Optional filter by items that have a tmdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? HasTmdbId { get; set; }
-
- [ApiMember(Name = "HasTvdbId", Description = "Optional filter by items that have a tvdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? HasTvdbId { get; set; }
-
- [ApiMember(Name = "ExcludeItemIds", Description = "Optional. If specified, results will be filtered by exxcluding item ids. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string ExcludeItemIds { get; set; }
-
- public bool EnableTotalRecordCount { get; set; }
-
- /// <summary>
- /// Skips over a given number of items within the results. Use for paging.
- /// </summary>
- /// <value>The start index.</value>
- [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? StartIndex { get; set; }
-
- /// <summary>
- /// The maximum number of items to return
- /// </summary>
- /// <value>The limit.</value>
- [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? Limit { get; set; }
-
- /// <summary>
- /// Whether or not to perform the query recursively
- /// </summary>
- /// <value><c>true</c> if recursive; otherwise, <c>false</c>.</value>
- [ApiMember(Name = "Recursive", Description = "When searching within folders, this determines whether or not the search will be recursive. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool Recursive { get; set; }
-
- public string SearchTerm { get; set; }
-
- /// <summary>
- /// Gets or sets the sort order.
- /// </summary>
- /// <value>The sort order.</value>
- [ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string SortOrder { get; set; }
-
- /// <summary>
- /// Specify this to localize the search to a specific item or folder. Omit to use the root.
- /// </summary>
- /// <value>The parent id.</value>
- [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string ParentId { get; set; }
-
- /// <summary>
- /// Fields to return within the items, in addition to basic information
- /// </summary>
- /// <value>The fields.</value>
- [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string Fields { get; set; }
-
- /// <summary>
- /// Gets or sets the exclude item types.
- /// </summary>
- /// <value>The exclude item types.</value>
- [ApiMember(Name = "ExcludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string ExcludeItemTypes { get; set; }
-
- /// <summary>
- /// Gets or sets the include item types.
- /// </summary>
- /// <value>The include item types.</value>
- [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string IncludeItemTypes { get; set; }
-
- /// <summary>
- /// Filters to apply to the results
- /// </summary>
- /// <value>The filters.</value>
- [ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string Filters { get; set; }
-
- /// <summary>
- /// Gets or sets the Isfavorite option
- /// </summary>
- /// <value>IsFavorite</value>
- [ApiMember(Name = "IsFavorite", Description = "Optional filter by items that are marked as favorite, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? IsFavorite { get; set; }
-
- /// <summary>
- /// Gets or sets the media types.
- /// </summary>
- /// <value>The media types.</value>
- [ApiMember(Name = "MediaTypes", Description = "Optional filter by MediaType. Allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string MediaTypes { get; set; }
-
- /// <summary>
- /// Gets or sets the image types.
- /// </summary>
- /// <value>The image types.</value>
- [ApiMember(Name = "ImageTypes", Description = "Optional. If specified, results will be filtered based on those containing image types. This allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string ImageTypes { get; set; }
-
- /// <summary>
- /// What to sort the results by
- /// </summary>
- /// <value>The sort by.</value>
- [ApiMember(Name = "SortBy", Description = "Optional. Specify one or more sort orders, comma delimeted. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string SortBy { get; set; }
-
- [ApiMember(Name = "IsPlayed", Description = "Optional filter by items that are played, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? IsPlayed { get; set; }
-
- /// <summary>
- /// Limit results to items containing specific genres
- /// </summary>
- /// <value>The genres.</value>
- [ApiMember(Name = "Genres", Description = "Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string Genres { get; set; }
-
- public string GenreIds { get; set; }
-
- [ApiMember(Name = "OfficialRatings", Description = "Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string OfficialRatings { get; set; }
-
- [ApiMember(Name = "Tags", Description = "Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string Tags { get; set; }
-
- /// <summary>
- /// Limit results to items containing specific years
- /// </summary>
- /// <value>The years.</value>
- [ApiMember(Name = "Years", Description = "Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string Years { get; set; }
-
- [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? EnableImages { get; set; }
-
- [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? EnableUserData { get; set; }
-
- [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? ImageTypeLimit { get; set; }
-
- [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string EnableImageTypes { get; set; }
-
- /// <summary>
- /// Limit results to items containing a specific person
- /// </summary>
- /// <value>The person.</value>
- [ApiMember(Name = "Person", Description = "Optional. If specified, results will be filtered to include only those containing the specified person.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string Person { get; set; }
-
- [ApiMember(Name = "PersonIds", Description = "Optional. If specified, results will be filtered to include only those containing the specified person.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string PersonIds { get; set; }
-
- /// <summary>
- /// If the Person filter is used, this can also be used to restrict to a specific person type
- /// </summary>
- /// <value>The type of the person.</value>
- [ApiMember(Name = "PersonTypes", Description = "Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string PersonTypes { get; set; }
-
- /// <summary>
- /// Limit results to items containing specific studios
- /// </summary>
- /// <value>The studios.</value>
- [ApiMember(Name = "Studios", Description = "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string Studios { get; set; }
-
- [ApiMember(Name = "StudioIds", Description = "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string StudioIds { get; set; }
-
- /// <summary>
- /// Gets or sets the studios.
- /// </summary>
- /// <value>The studios.</value>
- [ApiMember(Name = "Artists", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string Artists { get; set; }
-
- public string ExcludeArtistIds { get; set; }
-
- [ApiMember(Name = "ArtistIds", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string ArtistIds { get; set; }
-
- public string AlbumArtistIds { get; set; }
-
- public string ContributingArtistIds { get; set; }
-
- [ApiMember(Name = "Albums", Description = "Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string Albums { get; set; }
-
- public string AlbumIds { get; set; }
-
- /// <summary>
- /// Gets or sets the item ids.
- /// </summary>
- /// <value>The item ids.</value>
- [ApiMember(Name = "Ids", Description = "Optional. If specific items are needed, specify a list of item id's to retrieve. This allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string Ids { get; set; }
-
- /// <summary>
- /// Gets or sets the video types.
- /// </summary>
- /// <value>The video types.</value>
- [ApiMember(Name = "VideoTypes", Description = "Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string VideoTypes { get; set; }
-
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public Guid UserId { get; set; }
-
- /// <summary>
- /// Gets or sets the min offical rating.
- /// </summary>
- /// <value>The min offical rating.</value>
- [ApiMember(Name = "MinOfficialRating", Description = "Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string MinOfficialRating { get; set; }
-
- [ApiMember(Name = "IsLocked", Description = "Optional filter by items that are locked.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public bool? IsLocked { get; set; }
-
- [ApiMember(Name = "IsPlaceHolder", Description = "Optional filter by items that are placeholders", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public bool? IsPlaceHolder { get; set; }
-
- [ApiMember(Name = "HasOfficialRating", Description = "Optional filter by items that have official ratings", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public bool? HasOfficialRating { get; set; }
-
- [ApiMember(Name = "CollapseBoxSetItems", Description = "Whether or not to hide items behind their boxsets.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? CollapseBoxSetItems { get; set; }
-
- public int? MinWidth { get; set; }
- public int? MinHeight { get; set; }
- public int? MaxWidth { get; set; }
- public int? MaxHeight { get; set; }
-
- /// <summary>
- /// Gets or sets the video formats.
- /// </summary>
- /// <value>The video formats.</value>
- [ApiMember(Name = "Is3D", Description = "Optional filter by items that are 3D, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? Is3D { get; set; }
-
- /// <summary>
- /// Gets or sets the series status.
- /// </summary>
- /// <value>The series status.</value>
- [ApiMember(Name = "SeriesStatus", Description = "Optional filter by Series Status. Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string SeriesStatus { get; set; }
-
- [ApiMember(Name = "NameStartsWithOrGreater", Description = "Optional filter by items whose name is sorted equally or greater than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string NameStartsWithOrGreater { get; set; }
-
- [ApiMember(Name = "NameStartsWith", Description = "Optional filter by items whose name is sorted equally than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string NameStartsWith { get; set; }
-
- [ApiMember(Name = "NameLessThan", Description = "Optional filter by items whose name is equally or lesser than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string NameLessThan { get; set; }
-
- public string[] GetGenres()
- {
- return (Genres ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
- }
-
- public string[] GetTags()
- {
- return (Tags ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
- }
-
- public string[] GetOfficialRatings()
- {
- return (OfficialRatings ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
- }
-
- public string[] GetMediaTypes()
- {
- return (MediaTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
- }
-
- public string[] GetIncludeItemTypes()
- {
- return (IncludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
- }
-
- public string[] GetExcludeItemTypes()
- {
- return (ExcludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
- }
-
- public int[] GetYears()
- {
- return (Years ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray();
- }
-
- public string[] GetStudios()
- {
- return (Studios ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
- }
-
- public string[] GetPersonTypes()
- {
- return (PersonTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
- }
-
- public VideoType[] GetVideoTypes()
- {
- return string.IsNullOrEmpty(VideoTypes)
- ? Array.Empty<VideoType>()
- : VideoTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
- .Select(v => Enum.Parse<VideoType>(v, true)).ToArray();
- }
-
- /// <summary>
- /// Gets the filters.
- /// </summary>
- /// <returns>IEnumerable{ItemFilter}.</returns>
- public ItemFilter[] GetFilters()
- {
- var val = Filters;
-
- return string.IsNullOrEmpty(val)
- ? Array.Empty<ItemFilter>()
- : val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
- .Select(v => Enum.Parse<ItemFilter>(v, true)).ToArray();
- }
-
- /// <summary>
- /// Gets the image types.
- /// </summary>
- /// <returns>IEnumerable{ImageType}.</returns>
- public ImageType[] GetImageTypes()
- {
- var val = ImageTypes;
-
- return string.IsNullOrEmpty(val)
- ? Array.Empty<ImageType>()
- : val.Split(',').Select(v => Enum.Parse<ImageType>(v, true)).ToArray();
- }
-
- /// <summary>
- /// Gets the order by.
- /// </summary>
- /// <returns>IEnumerable{ItemSortBy}.</returns>
- public ValueTuple<string, SortOrder>[] GetOrderBy()
- {
- return GetOrderBy(SortBy, SortOrder);
- }
-
- public static ValueTuple<string, SortOrder>[] GetOrderBy(string sortBy, string requestedSortOrder)
- {
- var val = sortBy;
-
- if (string.IsNullOrEmpty(val))
- {
- return Array.Empty<ValueTuple<string, SortOrder>>();
- }
-
- var vals = val.Split(',');
- if (string.IsNullOrWhiteSpace(requestedSortOrder))
- {
- requestedSortOrder = "Ascending";
- }
-
- var sortOrders = requestedSortOrder.Split(',');
-
- var result = new ValueTuple<string, SortOrder>[vals.Length];
-
- for (var i = 0; i < vals.Length; i++)
- {
- var sortOrderIndex = sortOrders.Length > i ? i : 0;
-
- var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null;
- var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase)
- ? MediaBrowser.Model.Entities.SortOrder.Descending
- : MediaBrowser.Model.Entities.SortOrder.Ascending;
-
- result[i] = new ValueTuple<string, SortOrder>(vals[i], sortOrder);
- }
-
- return result;
- }
- }
-}
diff --git a/MediaBrowser.Api/UserLibrary/GenresService.cs b/MediaBrowser.Api/UserLibrary/GenresService.cs
deleted file mode 100644
index 1fa272a5f..000000000
--- a/MediaBrowser.Api/UserLibrary/GenresService.cs
+++ /dev/null
@@ -1,140 +0,0 @@
-using System;
-using System.Collections.Generic;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api.UserLibrary
-{
- /// <summary>
- /// Class GetGenres
- /// </summary>
- [Route("/Genres", "GET", Summary = "Gets all genres from a given item, folder, or the entire library")]
- public class GetGenres : GetItemsByName
- {
- }
-
- /// <summary>
- /// Class GetGenre
- /// </summary>
- [Route("/Genres/{Name}", "GET", Summary = "Gets a genre, by name")]
- public class GetGenre : IReturn<BaseItemDto>
- {
- /// <summary>
- /// Gets or sets the name.
- /// </summary>
- /// <value>The name.</value>
- [ApiMember(Name = "Name", Description = "The genre name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Name { get; set; }
-
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public Guid UserId { get; set; }
- }
-
- /// <summary>
- /// Class GenresService
- /// </summary>
- [Authenticated]
- public class GenresService : BaseItemsByNameService<Genre>
- {
- public GenresService(
- ILogger<GenresService> logger,
- IServerConfigurationManager serverConfigurationManager,
- IHttpResultFactory httpResultFactory,
- IUserManager userManager,
- ILibraryManager libraryManager,
- IUserDataManager userDataRepository,
- IDtoService dtoService,
- IAuthorizationContext authorizationContext)
- : base(
- logger,
- serverConfigurationManager,
- httpResultFactory,
- userManager,
- libraryManager,
- userDataRepository,
- dtoService,
- authorizationContext)
- {
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetGenre request)
- {
- var result = GetItem(request);
-
- return ToOptimizedResult(result);
- }
-
- /// <summary>
- /// Gets the item.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>Task{BaseItemDto}.</returns>
- private BaseItemDto GetItem(GetGenre request)
- {
- var dtoOptions = GetDtoOptions(AuthorizationContext, request);
-
- var item = GetGenre(request.Name, LibraryManager, dtoOptions);
-
- if (!request.UserId.Equals(Guid.Empty))
- {
- var user = UserManager.GetUserById(request.UserId);
-
- return DtoService.GetBaseItemDto(item, dtoOptions, user);
- }
-
- return DtoService.GetBaseItemDto(item, dtoOptions);
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetGenres request)
- {
- var result = GetResultSlim(request);
-
- return ToOptimizedResult(result);
- }
-
- protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
- {
- var viewType = GetParentItemViewType(request);
-
- if (string.Equals(viewType, CollectionType.Music) || string.Equals(viewType, CollectionType.MusicVideos))
- {
- return LibraryManager.GetMusicGenres(query);
- }
-
- return LibraryManager.GetGenres(query);
- }
-
- /// <summary>
- /// Gets all items.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="items">The items.</param>
- /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
- protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items)
- {
- throw new NotImplementedException();
- }
- }
-}
diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs
deleted file mode 100644
index 49d534c36..000000000
--- a/MediaBrowser.Api/UserLibrary/ItemsService.cs
+++ /dev/null
@@ -1,514 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using Jellyfin.Data.Entities;
-using Jellyfin.Data.Enums;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
-
-namespace MediaBrowser.Api.UserLibrary
-{
- /// <summary>
- /// Class GetItems
- /// </summary>
- [Route("/Items", "GET", Summary = "Gets items based on a query.")]
- [Route("/Users/{UserId}/Items", "GET", Summary = "Gets items based on a query.")]
- public class GetItems : BaseItemsRequest, IReturn<QueryResult<BaseItemDto>>
- {
- }
-
- [Route("/Users/{UserId}/Items/Resume", "GET", Summary = "Gets items based on a query.")]
- public class GetResumeItems : BaseItemsRequest, IReturn<QueryResult<BaseItemDto>>
- {
- }
-
- /// <summary>
- /// Class ItemsService
- /// </summary>
- [Authenticated]
- public class ItemsService : BaseApiService
- {
- /// <summary>
- /// The _user manager
- /// </summary>
- private readonly IUserManager _userManager;
-
- /// <summary>
- /// The _library manager
- /// </summary>
- private readonly ILibraryManager _libraryManager;
- private readonly ILocalizationManager _localization;
-
- private readonly IDtoService _dtoService;
- private readonly IAuthorizationContext _authContext;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="ItemsService" /> class.
- /// </summary>
- /// <param name="userManager">The user manager.</param>
- /// <param name="libraryManager">The library manager.</param>
- /// <param name="localization">The localization.</param>
- /// <param name="dtoService">The dto service.</param>
- public ItemsService(
- ILogger<ItemsService> logger,
- IServerConfigurationManager serverConfigurationManager,
- IHttpResultFactory httpResultFactory,
- IUserManager userManager,
- ILibraryManager libraryManager,
- ILocalizationManager localization,
- IDtoService dtoService,
- IAuthorizationContext authContext)
- : base(logger, serverConfigurationManager, httpResultFactory)
- {
- _userManager = userManager;
- _libraryManager = libraryManager;
- _localization = localization;
- _dtoService = dtoService;
- _authContext = authContext;
- }
-
- public object Get(GetResumeItems request)
- {
- var user = _userManager.GetUserById(request.UserId);
-
- var parentIdGuid = string.IsNullOrWhiteSpace(request.ParentId) ? Guid.Empty : new Guid(request.ParentId);
-
- var options = GetDtoOptions(_authContext, request);
-
- var ancestorIds = Array.Empty<Guid>();
-
- var excludeFolderIds = user.GetPreference(PreferenceKind.LatestItemExcludes);
- if (parentIdGuid.Equals(Guid.Empty) && excludeFolderIds.Length > 0)
- {
- ancestorIds = _libraryManager.GetUserRootFolder().GetChildren(user, true)
- .Where(i => i is Folder)
- .Where(i => !excludeFolderIds.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture)))
- .Select(i => i.Id)
- .ToArray();
- }
-
- var itemsResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
- {
- OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) },
- IsResumable = true,
- StartIndex = request.StartIndex,
- Limit = request.Limit,
- ParentId = parentIdGuid,
- Recursive = true,
- DtoOptions = options,
- MediaTypes = request.GetMediaTypes(),
- IsVirtualItem = false,
- CollapseBoxSetItems = false,
- EnableTotalRecordCount = request.EnableTotalRecordCount,
- AncestorIds = ancestorIds,
- IncludeItemTypes = request.GetIncludeItemTypes(),
- ExcludeItemTypes = request.GetExcludeItemTypes(),
- SearchTerm = request.SearchTerm
- });
-
- var returnItems = _dtoService.GetBaseItemDtos(itemsResult.Items, options, user);
-
- var result = new QueryResult<BaseItemDto>
- {
- StartIndex = request.StartIndex.GetValueOrDefault(),
- TotalRecordCount = itemsResult.TotalRecordCount,
- Items = returnItems
- };
-
- return ToOptimizedResult(result);
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetItems request)
- {
- if (request == null)
- {
- throw new ArgumentNullException(nameof(request));
- }
-
- var result = GetItems(request);
-
- return ToOptimizedResult(result);
- }
-
- /// <summary>
- /// Gets the items.
- /// </summary>
- /// <param name="request">The request.</param>
- private QueryResult<BaseItemDto> GetItems(GetItems request)
- {
- var user = request.UserId == Guid.Empty ? null : _userManager.GetUserById(request.UserId);
-
- var dtoOptions = GetDtoOptions(_authContext, request);
-
- var result = GetQueryResult(request, dtoOptions, user);
-
- if (result == null)
- {
- throw new InvalidOperationException("GetItemsToSerialize returned null");
- }
-
- if (result.Items == null)
- {
- throw new InvalidOperationException("GetItemsToSerialize result.Items returned null");
- }
-
- var dtoList = _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user);
-
- return new QueryResult<BaseItemDto>
- {
- StartIndex = request.StartIndex.GetValueOrDefault(),
- TotalRecordCount = result.TotalRecordCount,
- Items = dtoList
- };
- }
-
- /// <summary>
- /// Gets the items to serialize.
- /// </summary>
- private QueryResult<BaseItem> GetQueryResult(GetItems request, DtoOptions dtoOptions, User user)
- {
- if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase)
- || string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase))
- {
- request.ParentId = null;
- }
-
- BaseItem item = null;
-
- if (!string.IsNullOrEmpty(request.ParentId))
- {
- item = _libraryManager.GetItemById(request.ParentId);
- }
-
- if (item == null)
- {
- item = _libraryManager.GetUserRootFolder();
- }
-
- if (!(item is Folder folder))
- {
- folder = _libraryManager.GetUserRootFolder();
- }
-
- if (folder is IHasCollectionType hasCollectionType
- && string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
- {
- request.Recursive = true;
- request.IncludeItemTypes = "Playlist";
- }
-
- bool isInEnabledFolder = user.GetPreference(PreferenceKind.EnabledFolders).Any(i => new Guid(i) == item.Id)
- // Assume all folders inside an EnabledChannel are enabled
- || user.GetPreference(PreferenceKind.EnabledChannels).Any(i => new Guid(i) == item.Id);
-
- var collectionFolders = _libraryManager.GetCollectionFolders(item);
- foreach (var collectionFolder in collectionFolders)
- {
- if (user.GetPreference(PreferenceKind.EnabledFolders).Contains(
- collectionFolder.Id.ToString("N", CultureInfo.InvariantCulture),
- StringComparer.OrdinalIgnoreCase))
- {
- isInEnabledFolder = true;
- }
- }
-
- if (!(item is UserRootFolder)
- && !isInEnabledFolder
- && !user.HasPermission(PermissionKind.EnableAllFolders)
- && !user.HasPermission(PermissionKind.EnableAllChannels))
- {
- Logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Username, item.Name);
- return new QueryResult<BaseItem>
- {
- Items = Array.Empty<BaseItem>(),
- TotalRecordCount = 0,
- StartIndex = 0
- };
- }
-
- if (request.Recursive || !string.IsNullOrEmpty(request.Ids) || !(item is UserRootFolder))
- {
- return folder.GetItems(GetItemsQuery(request, dtoOptions, user));
- }
-
- var itemsArray = folder.GetChildren(user, true);
- return new QueryResult<BaseItem>
- {
- Items = itemsArray,
- TotalRecordCount = itemsArray.Count,
- StartIndex = 0
- };
- }
-
- private InternalItemsQuery GetItemsQuery(GetItems request, DtoOptions dtoOptions, User user)
- {
- var query = new InternalItemsQuery(user)
- {
- IsPlayed = request.IsPlayed,
- MediaTypes = request.GetMediaTypes(),
- IncludeItemTypes = request.GetIncludeItemTypes(),
- ExcludeItemTypes = request.GetExcludeItemTypes(),
- Recursive = request.Recursive,
- OrderBy = request.GetOrderBy(),
-
- IsFavorite = request.IsFavorite,
- Limit = request.Limit,
- StartIndex = request.StartIndex,
- IsMissing = request.IsMissing,
- IsUnaired = request.IsUnaired,
- CollapseBoxSetItems = request.CollapseBoxSetItems,
- NameLessThan = request.NameLessThan,
- NameStartsWith = request.NameStartsWith,
- NameStartsWithOrGreater = request.NameStartsWithOrGreater,
- HasImdbId = request.HasImdbId,
- IsPlaceHolder = request.IsPlaceHolder,
- IsLocked = request.IsLocked,
- MinWidth = request.MinWidth,
- MinHeight = request.MinHeight,
- MaxWidth = request.MaxWidth,
- MaxHeight = request.MaxHeight,
- Is3D = request.Is3D,
- HasTvdbId = request.HasTvdbId,
- HasTmdbId = request.HasTmdbId,
- HasOverview = request.HasOverview,
- HasOfficialRating = request.HasOfficialRating,
- HasParentalRating = request.HasParentalRating,
- HasSpecialFeature = request.HasSpecialFeature,
- HasSubtitles = request.HasSubtitles,
- HasThemeSong = request.HasThemeSong,
- HasThemeVideo = request.HasThemeVideo,
- HasTrailer = request.HasTrailer,
- IsHD = request.IsHD,
- Is4K = request.Is4K,
- Tags = request.GetTags(),
- OfficialRatings = request.GetOfficialRatings(),
- Genres = request.GetGenres(),
- ArtistIds = GetGuids(request.ArtistIds),
- AlbumArtistIds = GetGuids(request.AlbumArtistIds),
- ContributingArtistIds = GetGuids(request.ContributingArtistIds),
- GenreIds = GetGuids(request.GenreIds),
- StudioIds = GetGuids(request.StudioIds),
- Person = request.Person,
- PersonIds = GetGuids(request.PersonIds),
- PersonTypes = request.GetPersonTypes(),
- Years = request.GetYears(),
- ImageTypes = request.GetImageTypes(),
- VideoTypes = request.GetVideoTypes(),
- AdjacentTo = request.AdjacentTo,
- ItemIds = GetGuids(request.Ids),
- MinCommunityRating = request.MinCommunityRating,
- MinCriticRating = request.MinCriticRating,
- ParentId = string.IsNullOrWhiteSpace(request.ParentId) ? Guid.Empty : new Guid(request.ParentId),
- ParentIndexNumber = request.ParentIndexNumber,
- EnableTotalRecordCount = request.EnableTotalRecordCount,
- ExcludeItemIds = GetGuids(request.ExcludeItemIds),
- DtoOptions = dtoOptions,
- SearchTerm = request.SearchTerm
- };
-
- if (!string.IsNullOrWhiteSpace(request.Ids) || !string.IsNullOrWhiteSpace(request.SearchTerm))
- {
- query.CollapseBoxSetItems = false;
- }
-
- foreach (var filter in request.GetFilters())
- {
- 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;
- }
- }
-
- if (!string.IsNullOrEmpty(request.MinDateLastSaved))
- {
- query.MinDateLastSaved = DateTime.Parse(request.MinDateLastSaved, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
- }
-
- if (!string.IsNullOrEmpty(request.MinDateLastSavedForUser))
- {
- query.MinDateLastSavedForUser = DateTime.Parse(request.MinDateLastSavedForUser, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
- }
-
- if (!string.IsNullOrEmpty(request.MinPremiereDate))
- {
- query.MinPremiereDate = DateTime.Parse(request.MinPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
- }
-
- if (!string.IsNullOrEmpty(request.MaxPremiereDate))
- {
- query.MaxPremiereDate = DateTime.Parse(request.MaxPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
- }
-
- // Filter by Series Status
- if (!string.IsNullOrEmpty(request.SeriesStatus))
- {
- query.SeriesStatuses = request.SeriesStatus.Split(',').Select(d => (SeriesStatus)Enum.Parse(typeof(SeriesStatus), d, true)).ToArray();
- }
-
- // ExcludeLocationTypes
- if (!string.IsNullOrEmpty(request.ExcludeLocationTypes))
- {
- var excludeLocationTypes = request.ExcludeLocationTypes.Split(',').Select(d => (LocationType)Enum.Parse(typeof(LocationType), d, true)).ToArray();
- if (excludeLocationTypes.Contains(LocationType.Virtual))
- {
- query.IsVirtualItem = false;
- }
- }
-
- if (!string.IsNullOrEmpty(request.LocationTypes))
- {
- var requestedLocationTypes =
- request.LocationTypes.Split(',');
-
- if (requestedLocationTypes.Length > 0 && requestedLocationTypes.Length < 4)
- {
- query.IsVirtualItem = requestedLocationTypes.Contains(LocationType.Virtual.ToString());
- }
- }
-
- // Min official rating
- if (!string.IsNullOrWhiteSpace(request.MinOfficialRating))
- {
- query.MinParentalRating = _localization.GetRatingLevel(request.MinOfficialRating);
- }
-
- // Max official rating
- if (!string.IsNullOrWhiteSpace(request.MaxOfficialRating))
- {
- query.MaxParentalRating = _localization.GetRatingLevel(request.MaxOfficialRating);
- }
-
- // Artists
- if (!string.IsNullOrEmpty(request.Artists))
- {
- query.ArtistIds = request.Artists.Split('|').Select(i =>
- {
- try
- {
- return _libraryManager.GetArtist(i, new DtoOptions(false));
- }
- catch
- {
- return null;
- }
- }).Where(i => i != null).Select(i => i.Id).ToArray();
- }
-
- // ExcludeArtistIds
- if (!string.IsNullOrWhiteSpace(request.ExcludeArtistIds))
- {
- query.ExcludeArtistIds = GetGuids(request.ExcludeArtistIds);
- }
-
- if (!string.IsNullOrWhiteSpace(request.AlbumIds))
- {
- query.AlbumIds = GetGuids(request.AlbumIds);
- }
-
- // Albums
- if (!string.IsNullOrEmpty(request.Albums))
- {
- query.AlbumIds = request.Albums.Split('|').SelectMany(i =>
- {
- return _libraryManager.GetItemIds(new InternalItemsQuery
- {
- IncludeItemTypes = new[] { typeof(MusicAlbum).Name },
- Name = i,
- Limit = 1
- });
- }).ToArray();
- }
-
- // Studios
- if (!string.IsNullOrEmpty(request.Studios))
- {
- query.StudioIds = request.Studios.Split('|').Select(i =>
- {
- try
- {
- return _libraryManager.GetStudio(i);
- }
- catch
- {
- return null;
- }
- }).Where(i => i != 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 && string.Equals(query.IncludeItemTypes[0], "MusicAlbum", StringComparison.OrdinalIgnoreCase))
- {
- query.OrderBy = new[]
- {
- new ValueTuple<string, SortOrder>(ItemSortBy.ProductionYear, SortOrder.Descending),
- new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending)
- };
- }
- }
-
- return query;
- }
- }
-
- /// <summary>
- /// Class DateCreatedComparer
- /// </summary>
- public class DateCreatedComparer : IComparer<BaseItem>
- {
- /// <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 x.DateCreated.CompareTo(y.DateCreated);
- }
- }
-}
diff --git a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs
deleted file mode 100644
index e9caca14a..000000000
--- a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs
+++ /dev/null
@@ -1,124 +0,0 @@
-using System;
-using System.Collections.Generic;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api.UserLibrary
-{
- [Route("/MusicGenres", "GET", Summary = "Gets all music genres from a given item, folder, or the entire library")]
- public class GetMusicGenres : GetItemsByName
- {
- }
-
- [Route("/MusicGenres/{Name}", "GET", Summary = "Gets a music genre, by name")]
- public class GetMusicGenre : IReturn<BaseItemDto>
- {
- /// <summary>
- /// Gets or sets the name.
- /// </summary>
- /// <value>The name.</value>
- [ApiMember(Name = "Name", Description = "The genre name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Name { get; set; }
-
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public Guid UserId { get; set; }
- }
-
- [Authenticated]
- public class MusicGenresService : BaseItemsByNameService<MusicGenre>
- {
- public MusicGenresService(
- ILogger<MusicGenresService> logger,
- IServerConfigurationManager serverConfigurationManager,
- IHttpResultFactory httpResultFactory,
- IUserManager userManager,
- ILibraryManager libraryManager,
- IUserDataManager userDataRepository,
- IDtoService dtoService,
- IAuthorizationContext authorizationContext)
- : base(
- logger,
- serverConfigurationManager,
- httpResultFactory,
- userManager,
- libraryManager,
- userDataRepository,
- dtoService,
- authorizationContext)
- {
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetMusicGenre request)
- {
- var result = GetItem(request);
-
- return ToOptimizedResult(result);
- }
-
- /// <summary>
- /// Gets the item.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>Task{BaseItemDto}.</returns>
- private BaseItemDto GetItem(GetMusicGenre request)
- {
- var dtoOptions = GetDtoOptions(AuthorizationContext, request);
-
- var item = GetMusicGenre(request.Name, LibraryManager, dtoOptions);
-
- if (!request.UserId.Equals(Guid.Empty))
- {
- var user = UserManager.GetUserById(request.UserId);
-
- return DtoService.GetBaseItemDto(item, dtoOptions, user);
- }
-
- return DtoService.GetBaseItemDto(item, dtoOptions);
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetMusicGenres request)
- {
- var result = GetResultSlim(request);
-
- return ToOptimizedResult(result);
- }
-
- protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
- {
- return LibraryManager.GetMusicGenres(query);
- }
-
- /// <summary>
- /// Gets all items.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="items">The items.</param>
- /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
- protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items)
- {
- throw new NotImplementedException();
- }
- }
-}
diff --git a/MediaBrowser.Api/UserLibrary/PersonsService.cs b/MediaBrowser.Api/UserLibrary/PersonsService.cs
deleted file mode 100644
index 3204e5219..000000000
--- a/MediaBrowser.Api/UserLibrary/PersonsService.cs
+++ /dev/null
@@ -1,146 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api.UserLibrary
-{
- /// <summary>
- /// Class GetPersons
- /// </summary>
- [Route("/Persons", "GET", Summary = "Gets all persons from a given item, folder, or the entire library")]
- public class GetPersons : GetItemsByName
- {
- }
-
- /// <summary>
- /// Class GetPerson
- /// </summary>
- [Route("/Persons/{Name}", "GET", Summary = "Gets a person, by name")]
- public class GetPerson : IReturn<BaseItemDto>
- {
- /// <summary>
- /// Gets or sets the name.
- /// </summary>
- /// <value>The name.</value>
- [ApiMember(Name = "Name", Description = "The person name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Name { get; set; }
-
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public Guid UserId { get; set; }
- }
-
- /// <summary>
- /// Class PersonsService
- /// </summary>
- [Authenticated]
- public class PersonsService : BaseItemsByNameService<Person>
- {
- public PersonsService(
- ILogger<PersonsService> logger,
- IServerConfigurationManager serverConfigurationManager,
- IHttpResultFactory httpResultFactory,
- IUserManager userManager,
- ILibraryManager libraryManager,
- IUserDataManager userDataRepository,
- IDtoService dtoService,
- IAuthorizationContext authorizationContext)
- : base(
- logger,
- serverConfigurationManager,
- httpResultFactory,
- userManager,
- libraryManager,
- userDataRepository,
- dtoService,
- authorizationContext)
- {
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetPerson request)
- {
- var result = GetItem(request);
-
- return ToOptimizedResult(result);
- }
-
- /// <summary>
- /// Gets the item.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>Task{BaseItemDto}.</returns>
- private BaseItemDto GetItem(GetPerson request)
- {
- var dtoOptions = GetDtoOptions(AuthorizationContext, request);
-
- var item = GetPerson(request.Name, LibraryManager, dtoOptions);
-
- if (!request.UserId.Equals(Guid.Empty))
- {
- var user = UserManager.GetUserById(request.UserId);
-
- return DtoService.GetBaseItemDto(item, dtoOptions, user);
- }
-
- return DtoService.GetBaseItemDto(item, dtoOptions);
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetPersons request)
- {
- return GetResultSlim(request);
- }
-
- /// <summary>
- /// Gets all items.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="items">The items.</param>
- /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
- protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items)
- {
- throw new NotImplementedException();
- }
-
- protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
- {
- var items = LibraryManager.GetPeopleItems(new InternalPeopleQuery
- {
- PersonTypes = query.PersonTypes,
- NameContains = query.NameContains ?? query.SearchTerm
- });
-
- if ((query.IsFavorite ?? false) && query.User != null)
- {
- items = items.Where(i => UserDataRepository.GetUserData(query.User, i).IsFavorite).ToList();
- }
-
- return new QueryResult<(BaseItem, ItemCounts)>
- {
- TotalRecordCount = items.Count,
- Items = items.Take(query.Limit ?? int.MaxValue).Select(i => (i as BaseItem, new ItemCounts())).ToArray()
- };
- }
- }
-}
diff --git a/MediaBrowser.Api/UserLibrary/PlaystateService.cs b/MediaBrowser.Api/UserLibrary/PlaystateService.cs
deleted file mode 100644
index ab231626b..000000000
--- a/MediaBrowser.Api/UserLibrary/PlaystateService.cs
+++ /dev/null
@@ -1,456 +0,0 @@
-using System;
-using System.Globalization;
-using System.Threading.Tasks;
-using Jellyfin.Data.Entities;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Services;
-using MediaBrowser.Model.Session;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api.UserLibrary
-{
- /// <summary>
- /// Class MarkPlayedItem
- /// </summary>
- [Route("/Users/{UserId}/PlayedItems/{Id}", "POST", Summary = "Marks an item as played")]
- public class MarkPlayedItem : IReturn<UserItemDataDto>
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string UserId { get; set; }
-
- [ApiMember(Name = "DatePlayed", Description = "The date the item was played (if any). Format = yyyyMMddHHmmss", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string DatePlayed { get; set; }
-
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string Id { get; set; }
- }
-
- /// <summary>
- /// Class MarkUnplayedItem
- /// </summary>
- [Route("/Users/{UserId}/PlayedItems/{Id}", "DELETE", Summary = "Marks an item as unplayed")]
- public class MarkUnplayedItem : IReturn<UserItemDataDto>
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public string UserId { get; set; }
-
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public string Id { get; set; }
- }
-
- [Route("/Sessions/Playing", "POST", Summary = "Reports playback has started within a session")]
- public class ReportPlaybackStart : PlaybackStartInfo, IReturnVoid
- {
- }
-
- [Route("/Sessions/Playing/Progress", "POST", Summary = "Reports playback progress within a session")]
- public class ReportPlaybackProgress : PlaybackProgressInfo, IReturnVoid
- {
- }
-
- [Route("/Sessions/Playing/Ping", "POST", Summary = "Pings a playback session")]
- public class PingPlaybackSession : IReturnVoid
- {
- [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string PlaySessionId { get; set; }
- }
-
- [Route("/Sessions/Playing/Stopped", "POST", Summary = "Reports playback has stopped within a session")]
- public class ReportPlaybackStopped : PlaybackStopInfo, IReturnVoid
- {
- }
-
- /// <summary>
- /// Class OnPlaybackStart
- /// </summary>
- [Route("/Users/{UserId}/PlayingItems/{Id}", "POST", Summary = "Reports that a user has begun playing an item")]
- public class OnPlaybackStart : IReturnVoid
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string UserId { get; set; }
-
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string Id { get; set; }
-
- [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string MediaSourceId { get; set; }
-
- [ApiMember(Name = "CanSeek", Description = "Indicates if the client can seek", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
- public bool CanSeek { get; set; }
-
- [ApiMember(Name = "AudioStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
- public int? AudioStreamIndex { get; set; }
-
- [ApiMember(Name = "SubtitleStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
- public int? SubtitleStreamIndex { get; set; }
-
- [ApiMember(Name = "PlayMethod", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public PlayMethod PlayMethod { get; set; }
-
- [ApiMember(Name = "LiveStreamId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string LiveStreamId { get; set; }
-
- [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string PlaySessionId { get; set; }
- }
-
- /// <summary>
- /// Class OnPlaybackProgress
- /// </summary>
- [Route("/Users/{UserId}/PlayingItems/{Id}/Progress", "POST", Summary = "Reports a user's playback progress")]
- public class OnPlaybackProgress : IReturnVoid
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string UserId { get; set; }
-
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string Id { get; set; }
-
- [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string MediaSourceId { get; set; }
-
- /// <summary>
- /// Gets or sets the position ticks.
- /// </summary>
- /// <value>The position ticks.</value>
- [ApiMember(Name = "PositionTicks", Description = "Optional. The current position, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
- public long? PositionTicks { get; set; }
-
- [ApiMember(Name = "IsPaused", Description = "Indicates if the player is paused.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
- public bool IsPaused { get; set; }
-
- [ApiMember(Name = "IsMuted", Description = "Indicates if the player is muted.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
- public bool IsMuted { get; set; }
-
- [ApiMember(Name = "AudioStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
- public int? AudioStreamIndex { get; set; }
-
- [ApiMember(Name = "SubtitleStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
- public int? SubtitleStreamIndex { get; set; }
-
- [ApiMember(Name = "VolumeLevel", Description = "Scale of 0-100", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
- public int? VolumeLevel { get; set; }
-
- [ApiMember(Name = "PlayMethod", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public PlayMethod PlayMethod { get; set; }
-
- [ApiMember(Name = "LiveStreamId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string LiveStreamId { get; set; }
-
- [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string PlaySessionId { get; set; }
-
- [ApiMember(Name = "RepeatMode", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public RepeatMode RepeatMode { get; set; }
- }
-
- /// <summary>
- /// Class OnPlaybackStopped
- /// </summary>
- [Route("/Users/{UserId}/PlayingItems/{Id}", "DELETE", Summary = "Reports that a user has stopped playing an item")]
- public class OnPlaybackStopped : IReturnVoid
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public string UserId { get; set; }
-
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public string Id { get; set; }
-
- [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
- public string MediaSourceId { get; set; }
-
- [ApiMember(Name = "NextMediaType", Description = "The next media type that will play", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
- public string NextMediaType { get; set; }
-
- /// <summary>
- /// Gets or sets the position ticks.
- /// </summary>
- /// <value>The position ticks.</value>
- [ApiMember(Name = "PositionTicks", Description = "Optional. The position, in ticks, where playback stopped. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "DELETE")]
- public long? PositionTicks { get; set; }
-
- [ApiMember(Name = "LiveStreamId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string LiveStreamId { get; set; }
-
- [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string PlaySessionId { get; set; }
- }
-
- [Authenticated]
- public class PlaystateService : BaseApiService
- {
- private readonly IUserManager _userManager;
- private readonly IUserDataManager _userDataRepository;
- private readonly ILibraryManager _libraryManager;
- private readonly ISessionManager _sessionManager;
- private readonly ISessionContext _sessionContext;
- private readonly IAuthorizationContext _authContext;
-
- public PlaystateService(
- ILogger<PlaystateService> logger,
- IServerConfigurationManager serverConfigurationManager,
- IHttpResultFactory httpResultFactory,
- IUserManager userManager,
- IUserDataManager userDataRepository,
- ILibraryManager libraryManager,
- ISessionManager sessionManager,
- ISessionContext sessionContext,
- IAuthorizationContext authContext)
- : base(logger, serverConfigurationManager, httpResultFactory)
- {
- _userManager = userManager;
- _userDataRepository = userDataRepository;
- _libraryManager = libraryManager;
- _sessionManager = sessionManager;
- _sessionContext = sessionContext;
- _authContext = authContext;
- }
-
- /// <summary>
- /// Posts the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- public object Post(MarkPlayedItem request)
- {
- var result = MarkPlayed(request);
-
- return ToOptimizedResult(result);
- }
-
- private UserItemDataDto MarkPlayed(MarkPlayedItem request)
- {
- var user = _userManager.GetUserById(Guid.Parse(request.UserId));
-
- DateTime? datePlayed = null;
-
- if (!string.IsNullOrEmpty(request.DatePlayed))
- {
- datePlayed = DateTime.ParseExact(request.DatePlayed, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
- }
-
- var session = GetSession(_sessionContext);
-
- var dto = UpdatePlayedStatus(user, request.Id, true, datePlayed);
-
- foreach (var additionalUserInfo in session.AdditionalUsers)
- {
- var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId);
-
- UpdatePlayedStatus(additionalUser, request.Id, true, datePlayed);
- }
-
- return dto;
- }
-
- private PlayMethod ValidatePlayMethod(PlayMethod method, string playSessionId)
- {
- if (method == PlayMethod.Transcode)
- {
- var job = string.IsNullOrWhiteSpace(playSessionId) ? null : ApiEntryPoint.Instance.GetTranscodingJob(playSessionId);
- if (job == null)
- {
- return PlayMethod.DirectPlay;
- }
- }
-
- return method;
- }
-
- /// <summary>
- /// Posts the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- public void Post(OnPlaybackStart request)
- {
- Post(new ReportPlaybackStart
- {
- CanSeek = request.CanSeek,
- ItemId = new Guid(request.Id),
- MediaSourceId = request.MediaSourceId,
- AudioStreamIndex = request.AudioStreamIndex,
- SubtitleStreamIndex = request.SubtitleStreamIndex,
- PlayMethod = request.PlayMethod,
- PlaySessionId = request.PlaySessionId,
- LiveStreamId = request.LiveStreamId
- });
- }
-
- public void Post(ReportPlaybackStart request)
- {
- request.PlayMethod = ValidatePlayMethod(request.PlayMethod, request.PlaySessionId);
-
- request.SessionId = GetSession(_sessionContext).Id;
-
- var task = _sessionManager.OnPlaybackStart(request);
-
- Task.WaitAll(task);
- }
-
- /// <summary>
- /// Posts the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- public void Post(OnPlaybackProgress request)
- {
- Post(new ReportPlaybackProgress
- {
- ItemId = new Guid(request.Id),
- PositionTicks = request.PositionTicks,
- IsMuted = request.IsMuted,
- IsPaused = request.IsPaused,
- MediaSourceId = request.MediaSourceId,
- AudioStreamIndex = request.AudioStreamIndex,
- SubtitleStreamIndex = request.SubtitleStreamIndex,
- VolumeLevel = request.VolumeLevel,
- PlayMethod = request.PlayMethod,
- PlaySessionId = request.PlaySessionId,
- LiveStreamId = request.LiveStreamId,
- RepeatMode = request.RepeatMode
- });
- }
-
- public void Post(ReportPlaybackProgress request)
- {
- request.PlayMethod = ValidatePlayMethod(request.PlayMethod, request.PlaySessionId);
-
- request.SessionId = GetSession(_sessionContext).Id;
-
- var task = _sessionManager.OnPlaybackProgress(request);
-
- Task.WaitAll(task);
- }
-
- public void Post(PingPlaybackSession request)
- {
- ApiEntryPoint.Instance.PingTranscodingJob(request.PlaySessionId, null);
- }
-
- /// <summary>
- /// Posts the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- public Task Delete(OnPlaybackStopped request)
- {
- return Post(new ReportPlaybackStopped
- {
- ItemId = new Guid(request.Id),
- PositionTicks = request.PositionTicks,
- MediaSourceId = request.MediaSourceId,
- PlaySessionId = request.PlaySessionId,
- LiveStreamId = request.LiveStreamId,
- NextMediaType = request.NextMediaType
- });
- }
-
- public async Task Post(ReportPlaybackStopped request)
- {
- Logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", request.PlaySessionId ?? string.Empty);
-
- if (!string.IsNullOrWhiteSpace(request.PlaySessionId))
- {
- await ApiEntryPoint.Instance.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId, s => true);
- }
-
- request.SessionId = GetSession(_sessionContext).Id;
-
- await _sessionManager.OnPlaybackStopped(request);
- }
-
- /// <summary>
- /// Deletes the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- public object Delete(MarkUnplayedItem request)
- {
- var task = MarkUnplayed(request);
-
- return ToOptimizedResult(task);
- }
-
- private UserItemDataDto MarkUnplayed(MarkUnplayedItem request)
- {
- var user = _userManager.GetUserById(Guid.Parse(request.UserId));
-
- var session = GetSession(_sessionContext);
-
- var dto = UpdatePlayedStatus(user, request.Id, false, null);
-
- foreach (var additionalUserInfo in session.AdditionalUsers)
- {
- var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId);
-
- UpdatePlayedStatus(additionalUser, request.Id, false, null);
- }
-
- return dto;
- }
-
- /// <summary>
- /// Updates the played status.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <param name="itemId">The item id.</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, string itemId, bool wasPlayed, DateTime? datePlayed)
- {
- var item = _libraryManager.GetItemById(itemId);
-
- if (wasPlayed)
- {
- item.MarkPlayed(user, datePlayed, true);
- }
- else
- {
- item.MarkUnplayed(user);
- }
-
- return _userDataRepository.GetUserDataDto(item, user);
- }
- }
-}
diff --git a/MediaBrowser.Api/UserLibrary/StudiosService.cs b/MediaBrowser.Api/UserLibrary/StudiosService.cs
deleted file mode 100644
index 683ce5d09..000000000
--- a/MediaBrowser.Api/UserLibrary/StudiosService.cs
+++ /dev/null
@@ -1,132 +0,0 @@
-using System;
-using System.Collections.Generic;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api.UserLibrary
-{
- /// <summary>
- /// Class GetStudios
- /// </summary>
- [Route("/Studios", "GET", Summary = "Gets all studios from a given item, folder, or the entire library")]
- public class GetStudios : GetItemsByName
- {
- }
-
- /// <summary>
- /// Class GetStudio
- /// </summary>
- [Route("/Studios/{Name}", "GET", Summary = "Gets a studio, by name")]
- public class GetStudio : IReturn<BaseItemDto>
- {
- /// <summary>
- /// Gets or sets the name.
- /// </summary>
- /// <value>The name.</value>
- [ApiMember(Name = "Name", Description = "The studio name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Name { get; set; }
-
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public Guid UserId { get; set; }
- }
-
- /// <summary>
- /// Class StudiosService
- /// </summary>
- [Authenticated]
- public class StudiosService : BaseItemsByNameService<Studio>
- {
- public StudiosService(
- ILogger<StudiosService> logger,
- IServerConfigurationManager serverConfigurationManager,
- IHttpResultFactory httpResultFactory,
- IUserManager userManager,
- ILibraryManager libraryManager,
- IUserDataManager userDataRepository,
- IDtoService dtoService,
- IAuthorizationContext authorizationContext)
- : base(
- logger,
- serverConfigurationManager,
- httpResultFactory,
- userManager,
- libraryManager,
- userDataRepository,
- dtoService,
- authorizationContext)
- {
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetStudio request)
- {
- var result = GetItem(request);
-
- return ToOptimizedResult(result);
- }
-
- /// <summary>
- /// Gets the item.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>Task{BaseItemDto}.</returns>
- private BaseItemDto GetItem(GetStudio request)
- {
- var dtoOptions = GetDtoOptions(AuthorizationContext, request);
-
- var item = GetStudio(request.Name, LibraryManager, dtoOptions);
-
- if (!request.UserId.Equals(Guid.Empty))
- {
- var user = UserManager.GetUserById(request.UserId);
-
- return DtoService.GetBaseItemDto(item, dtoOptions, user);
- }
-
- return DtoService.GetBaseItemDto(item, dtoOptions);
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetStudios request)
- {
- var result = GetResultSlim(request);
-
- return ToOptimizedResult(result);
- }
-
- protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
- {
- return LibraryManager.GetStudios(query);
- }
-
- /// <summary>
- /// Gets all items.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="items">The items.</param>
- /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
- protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items)
- {
- throw new NotImplementedException();
- }
- }
-}
diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs
deleted file mode 100644
index f75852885..000000000
--- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs
+++ /dev/null
@@ -1,575 +0,0 @@
-using System;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api.UserLibrary
-{
- /// <summary>
- /// Class GetItem
- /// </summary>
- [Route("/Users/{UserId}/Items/{Id}", "GET", Summary = "Gets an item from a user's library")]
- public class GetItem : IReturn<BaseItemDto>
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public Guid UserId { get; set; }
-
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
- }
-
- /// <summary>
- /// Class GetItem
- /// </summary>
- [Route("/Users/{UserId}/Items/Root", "GET", Summary = "Gets the root folder from a user's library")]
- public class GetRootFolder : IReturn<BaseItemDto>
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public Guid UserId { get; set; }
- }
-
- /// <summary>
- /// Class GetIntros
- /// </summary>
- [Route("/Users/{UserId}/Items/{Id}/Intros", "GET", Summary = "Gets intros to play before the main media item plays")]
- public class GetIntros : IReturn<QueryResult<BaseItemDto>>
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public Guid UserId { get; set; }
-
- /// <summary>
- /// Gets or sets the item id.
- /// </summary>
- /// <value>The item id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
- }
-
- /// <summary>
- /// Class MarkFavoriteItem
- /// </summary>
- [Route("/Users/{UserId}/FavoriteItems/{Id}", "POST", Summary = "Marks an item as a favorite")]
- public class MarkFavoriteItem : IReturn<UserItemDataDto>
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public Guid UserId { get; set; }
-
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public Guid Id { get; set; }
- }
-
- /// <summary>
- /// Class UnmarkFavoriteItem
- /// </summary>
- [Route("/Users/{UserId}/FavoriteItems/{Id}", "DELETE", Summary = "Unmarks an item as a favorite")]
- public class UnmarkFavoriteItem : IReturn<UserItemDataDto>
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public Guid UserId { get; set; }
-
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public Guid Id { get; set; }
- }
-
- /// <summary>
- /// Class ClearUserItemRating
- /// </summary>
- [Route("/Users/{UserId}/Items/{Id}/Rating", "DELETE", Summary = "Deletes a user's saved personal rating for an item")]
- public class DeleteUserItemRating : IReturn<UserItemDataDto>
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public Guid UserId { get; set; }
-
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public Guid Id { get; set; }
- }
-
- /// <summary>
- /// Class UpdateUserItemRating
- /// </summary>
- [Route("/Users/{UserId}/Items/{Id}/Rating", "POST", Summary = "Updates a user's rating for an item")]
- public class UpdateUserItemRating : IReturn<UserItemDataDto>
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public Guid UserId { get; set; }
-
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public Guid Id { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether this <see cref="UpdateUserItemRating" /> is likes.
- /// </summary>
- /// <value><c>true</c> if likes; otherwise, <c>false</c>.</value>
- [ApiMember(Name = "Likes", Description = "Whether the user likes the item or not. true/false", IsRequired = true, DataType = "boolean", ParameterType = "query", Verb = "POST")]
- public bool Likes { get; set; }
- }
-
- /// <summary>
- /// Class GetLocalTrailers
- /// </summary>
- [Route("/Users/{UserId}/Items/{Id}/LocalTrailers", "GET", Summary = "Gets local trailers for an item")]
- public class GetLocalTrailers : IReturn<BaseItemDto[]>
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public Guid UserId { get; set; }
-
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
- }
-
- /// <summary>
- /// Class GetSpecialFeatures
- /// </summary>
- [Route("/Users/{UserId}/Items/{Id}/SpecialFeatures", "GET", Summary = "Gets special features for an item")]
- public class GetSpecialFeatures : IReturn<BaseItemDto[]>
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public Guid UserId { get; set; }
-
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Movie Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
- }
-
- [Route("/Users/{UserId}/Items/Latest", "GET", Summary = "Gets latest media")]
- public class GetLatestMedia : IReturn<BaseItemDto[]>, IHasDtoOptions
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public Guid UserId { get; set; }
-
- [ApiMember(Name = "Limit", Description = "Limit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int Limit { get; set; }
-
- [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public Guid ParentId { get; set; }
-
- [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string Fields { get; set; }
-
- [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string IncludeItemTypes { get; set; }
-
- [ApiMember(Name = "IsFolder", Description = "Filter by items that are folders, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? IsFolder { get; set; }
-
- [ApiMember(Name = "IsPlayed", Description = "Filter by items that are played, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? IsPlayed { get; set; }
-
- [ApiMember(Name = "GroupItems", Description = "Whether or not to group items into a parent container.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool GroupItems { get; set; }
-
- [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? EnableImages { get; set; }
-
- [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? ImageTypeLimit { get; set; }
-
- [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string EnableImageTypes { get; set; }
-
- [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? EnableUserData { get; set; }
-
- public GetLatestMedia()
- {
- Limit = 20;
- GroupItems = true;
- }
- }
-
- /// <summary>
- /// Class UserLibraryService
- /// </summary>
- [Authenticated]
- public class UserLibraryService : BaseApiService
- {
- private readonly IUserManager _userManager;
- private readonly IUserDataManager _userDataRepository;
- private readonly ILibraryManager _libraryManager;
- private readonly IDtoService _dtoService;
- private readonly IUserViewManager _userViewManager;
- private readonly IFileSystem _fileSystem;
- private readonly IAuthorizationContext _authContext;
-
- public UserLibraryService(
- ILogger<UserLibraryService> logger,
- IServerConfigurationManager serverConfigurationManager,
- IHttpResultFactory httpResultFactory,
- IUserManager userManager,
- ILibraryManager libraryManager,
- IUserDataManager userDataRepository,
- IDtoService dtoService,
- IUserViewManager userViewManager,
- IFileSystem fileSystem,
- IAuthorizationContext authContext)
- : base(logger, serverConfigurationManager, httpResultFactory)
- {
- _userManager = userManager;
- _libraryManager = libraryManager;
- _userDataRepository = userDataRepository;
- _dtoService = dtoService;
- _userViewManager = userViewManager;
- _fileSystem = fileSystem;
- _authContext = authContext;
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetSpecialFeatures request)
- {
- var result = GetAsync(request);
-
- return ToOptimizedResult(result);
- }
-
- public object Get(GetLatestMedia request)
- {
- var user = _userManager.GetUserById(request.UserId);
-
- if (!request.IsPlayed.HasValue)
- {
- if (user.HidePlayedInLatest)
- {
- request.IsPlayed = false;
- }
- }
-
- var dtoOptions = GetDtoOptions(_authContext, request);
-
- var list = _userViewManager.GetLatestItems(new LatestItemsQuery
- {
- GroupItems = request.GroupItems,
- IncludeItemTypes = ApiEntryPoint.Split(request.IncludeItemTypes, ',', true),
- IsPlayed = request.IsPlayed,
- Limit = request.Limit,
- ParentId = request.ParentId,
- UserId = request.UserId,
- }, dtoOptions);
-
- var dtos = list.Select(i =>
- {
- var item = i.Item2[0];
- var childCount = 0;
-
- if (i.Item1 != null && (i.Item2.Count > 1 || i.Item1 is MusicAlbum))
- {
- item = i.Item1;
- childCount = i.Item2.Count;
- }
-
- var dto = _dtoService.GetBaseItemDto(item, dtoOptions, user);
-
- dto.ChildCount = childCount;
-
- return dto;
- });
-
- return ToOptimizedResult(dtos.ToArray());
- }
-
- private BaseItemDto[] GetAsync(GetSpecialFeatures request)
- {
- var user = _userManager.GetUserById(request.UserId);
-
- var item = string.IsNullOrEmpty(request.Id) ?
- _libraryManager.GetUserRootFolder() :
- _libraryManager.GetItemById(request.Id);
-
- var dtoOptions = GetDtoOptions(_authContext, request);
-
- var dtos = item
- .GetExtras(BaseItem.DisplayExtraTypes)
- .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
-
- return dtos.ToArray();
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetLocalTrailers request)
- {
- var user = _userManager.GetUserById(request.UserId);
-
- var item = string.IsNullOrEmpty(request.Id) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(request.Id);
-
- var dtoOptions = GetDtoOptions(_authContext, request);
-
- var dtosExtras = item.GetExtras(new[] { ExtraType.Trailer })
- .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
- .ToArray();
-
- if (item is IHasTrailers hasTrailers)
- {
- var trailers = hasTrailers.GetTrailers();
- var dtosTrailers = _dtoService.GetBaseItemDtos(trailers, dtoOptions, user, item);
- var allTrailers = new BaseItemDto[dtosExtras.Length + dtosTrailers.Count];
- dtosExtras.CopyTo(allTrailers, 0);
- dtosTrailers.CopyTo(allTrailers, dtosExtras.Length);
- return ToOptimizedResult(allTrailers);
- }
-
- return ToOptimizedResult(dtosExtras);
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public async Task<object> Get(GetItem request)
- {
- var user = _userManager.GetUserById(request.UserId);
-
- var item = string.IsNullOrEmpty(request.Id) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(request.Id);
-
- await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false);
-
- var dtoOptions = GetDtoOptions(_authContext, request);
-
- var result = _dtoService.GetBaseItemDto(item, dtoOptions, user);
-
- return ToOptimizedResult(result);
- }
-
- private async Task RefreshItemOnDemandIfNeeded(BaseItem item)
- {
- if (item is Person)
- {
- var hasMetdata = !string.IsNullOrWhiteSpace(item.Overview) && item.HasImage(ImageType.Primary);
- var performFullRefresh = !hasMetdata && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= 3;
-
- if (!hasMetdata)
- {
- var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem))
- {
- MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
- ImageRefreshMode = MetadataRefreshMode.FullRefresh,
- ForceSave = performFullRefresh
- };
-
- await item.RefreshMetadata(options, CancellationToken.None).ConfigureAwait(false);
- }
- }
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetRootFolder request)
- {
- var user = _userManager.GetUserById(request.UserId);
-
- var item = _libraryManager.GetUserRootFolder();
-
- var dtoOptions = GetDtoOptions(_authContext, request);
-
- var result = _dtoService.GetBaseItemDto(item, dtoOptions, user);
-
- return ToOptimizedResult(result);
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public async Task<object> Get(GetIntros request)
- {
- var user = _userManager.GetUserById(request.UserId);
-
- var item = string.IsNullOrEmpty(request.Id) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(request.Id);
-
- var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false);
-
- var dtoOptions = GetDtoOptions(_authContext, request);
-
- var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray();
-
- var result = new QueryResult<BaseItemDto>
- {
- Items = dtos,
- TotalRecordCount = dtos.Length
- };
-
- return ToOptimizedResult(result);
- }
-
- /// <summary>
- /// Posts the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- public object Post(MarkFavoriteItem request)
- {
- var dto = MarkFavorite(request.UserId, request.Id, true);
-
- return ToOptimizedResult(dto);
- }
-
- /// <summary>
- /// Deletes the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- public object Delete(UnmarkFavoriteItem request)
- {
- var dto = MarkFavorite(request.UserId, request.Id, false);
-
- return ToOptimizedResult(dto);
- }
-
- /// <summary>
- /// Marks the favorite.
- /// </summary>
- /// <param name="userId">The user id.</param>
- /// <param name="itemId">The item id.</param>
- /// <param name="isFavorite">if set to <c>true</c> [is favorite].</param>
- private UserItemDataDto MarkFavorite(Guid userId, Guid itemId, bool isFavorite)
- {
- var user = _userManager.GetUserById(userId);
-
- var item = itemId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId);
-
- // Get the user data for this item
- var data = _userDataRepository.GetUserData(user, item);
-
- // Set favorite status
- data.IsFavorite = isFavorite;
-
- _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None);
-
- return _userDataRepository.GetUserDataDto(item, user);
- }
-
- /// <summary>
- /// Deletes the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- public object Delete(DeleteUserItemRating request)
- {
- var dto = UpdateUserItemRating(request.UserId, request.Id, null);
-
- return ToOptimizedResult(dto);
- }
-
- /// <summary>
- /// Posts the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- public object Post(UpdateUserItemRating request)
- {
- var dto = UpdateUserItemRating(request.UserId, request.Id, request.Likes);
-
- return ToOptimizedResult(dto);
- }
-
- /// <summary>
- /// Updates the user item rating.
- /// </summary>
- /// <param name="userId">The user id.</param>
- /// <param name="itemId">The item id.</param>
- /// <param name="likes">if set to <c>true</c> [likes].</param>
- private UserItemDataDto UpdateUserItemRating(Guid userId, Guid itemId, bool? likes)
- {
- var user = _userManager.GetUserById(userId);
-
- var item = itemId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId);
-
- // Get the user data for this item
- var data = _userDataRepository.GetUserData(user, item);
-
- data.Likes = likes;
-
- _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None);
-
- return _userDataRepository.GetUserDataDto(item, user);
- }
- }
-}
diff --git a/MediaBrowser.Api/UserLibrary/UserViewsService.cs b/MediaBrowser.Api/UserLibrary/UserViewsService.cs
deleted file mode 100644
index 0fffb0622..000000000
--- a/MediaBrowser.Api/UserLibrary/UserViewsService.cs
+++ /dev/null
@@ -1,146 +0,0 @@
-using System;
-using System.Globalization;
-using System.Linq;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Library;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api.UserLibrary
-{
- [Route("/Users/{UserId}/Views", "GET")]
- public class GetUserViews : IReturn<QueryResult<BaseItemDto>>
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public Guid UserId { get; set; }
-
- [ApiMember(Name = "IncludeExternalContent", Description = "Whether or not to include external views such as channels or live tv", IsRequired = true, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? IncludeExternalContent { get; set; }
- public bool IncludeHidden { get; set; }
-
- public string PresetViews { get; set; }
- }
-
- [Route("/Users/{UserId}/GroupingOptions", "GET")]
- public class GetGroupingOptions : IReturn<SpecialViewOption[]>
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public Guid UserId { get; set; }
- }
-
- public class UserViewsService : BaseApiService
- {
- private readonly IUserManager _userManager;
- private readonly IUserViewManager _userViewManager;
- private readonly IDtoService _dtoService;
- private readonly IAuthorizationContext _authContext;
- private readonly ILibraryManager _libraryManager;
-
- public UserViewsService(
- ILogger<UserViewsService> logger,
- IServerConfigurationManager serverConfigurationManager,
- IHttpResultFactory httpResultFactory,
- IUserManager userManager,
- IUserViewManager userViewManager,
- IDtoService dtoService,
- IAuthorizationContext authContext,
- ILibraryManager libraryManager)
- : base(logger, serverConfigurationManager, httpResultFactory)
- {
- _userManager = userManager;
- _userViewManager = userViewManager;
- _dtoService = dtoService;
- _authContext = authContext;
- _libraryManager = libraryManager;
- }
-
- public object Get(GetUserViews request)
- {
- var query = new UserViewQuery
- {
- UserId = request.UserId
- };
-
- if (request.IncludeExternalContent.HasValue)
- {
- query.IncludeExternalContent = request.IncludeExternalContent.Value;
- }
- query.IncludeHidden = request.IncludeHidden;
-
- if (!string.IsNullOrWhiteSpace(request.PresetViews))
- {
- query.PresetViews = request.PresetViews.Split(',');
- }
-
- var app = _authContext.GetAuthorizationInfo(Request).Client ?? string.Empty;
- if (app.IndexOf("emby rt", StringComparison.OrdinalIgnoreCase) != -1)
- {
- query.PresetViews = new[] { CollectionType.Movies, CollectionType.TvShows };
- }
-
- var folders = _userViewManager.GetUserViews(query);
-
- var dtoOptions = GetDtoOptions(_authContext, request);
- var fields = dtoOptions.Fields.ToList();
-
- fields.Add(ItemFields.PrimaryImageAspectRatio);
- fields.Add(ItemFields.DisplayPreferencesId);
- fields.Remove(ItemFields.BasicSyncInfo);
- dtoOptions.Fields = fields.ToArray();
-
- var user = _userManager.GetUserById(request.UserId);
-
- var dtos = folders.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user))
- .ToArray();
-
- var result = new QueryResult<BaseItemDto>
- {
- Items = dtos,
- TotalRecordCount = dtos.Length
- };
-
- return ToOptimizedResult(result);
- }
-
- public object Get(GetGroupingOptions request)
- {
- var user = _userManager.GetUserById(request.UserId);
-
- var list = _libraryManager.GetUserRootFolder()
- .GetChildren(user, true)
- .OfType<Folder>()
- .Where(UserView.IsEligibleForGrouping)
- .Select(i => new SpecialViewOption
- {
- Name = i.Name,
- Id = i.Id.ToString("N", CultureInfo.InvariantCulture)
-
- })
- .OrderBy(i => i.Name)
- .ToArray();
-
- return ToOptimizedResult(list);
- }
- }
-
- class SpecialViewOption
- {
- public string Name { get; set; }
- public string Id { get; set; }
- }
-}
diff --git a/MediaBrowser.Api/UserLibrary/YearsService.cs b/MediaBrowser.Api/UserLibrary/YearsService.cs
deleted file mode 100644
index d023ee90a..000000000
--- a/MediaBrowser.Api/UserLibrary/YearsService.cs
+++ /dev/null
@@ -1,131 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api.UserLibrary
-{
- /// <summary>
- /// Class GetYears
- /// </summary>
- [Route("/Years", "GET", Summary = "Gets all years from a given item, folder, or the entire library")]
- public class GetYears : GetItemsByName
- {
- }
-
- /// <summary>
- /// Class GetYear
- /// </summary>
- [Route("/Years/{Year}", "GET", Summary = "Gets a year")]
- public class GetYear : IReturn<BaseItemDto>
- {
- /// <summary>
- /// Gets or sets the year.
- /// </summary>
- /// <value>The year.</value>
- [ApiMember(Name = "Year", Description = "The year", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
- public int Year { get; set; }
-
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public Guid UserId { get; set; }
- }
-
- /// <summary>
- /// Class YearsService
- /// </summary>
- [Authenticated]
- public class YearsService : BaseItemsByNameService<Year>
- {
- public YearsService(
- ILogger<YearsService> logger,
- IServerConfigurationManager serverConfigurationManager,
- IHttpResultFactory httpResultFactory,
- IUserManager userManager,
- ILibraryManager libraryManager,
- IUserDataManager userDataRepository,
- IDtoService dtoService,
- IAuthorizationContext authorizationContext)
- : base(
- logger,
- serverConfigurationManager,
- httpResultFactory,
- userManager,
- libraryManager,
- userDataRepository,
- dtoService,
- authorizationContext)
- {
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetYear request)
- {
- var result = GetItem(request);
-
- return ToOptimizedResult(result);
- }
-
- /// <summary>
- /// Gets the item.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>Task{BaseItemDto}.</returns>
- private BaseItemDto GetItem(GetYear request)
- {
- var item = LibraryManager.GetYear(request.Year);
-
- var dtoOptions = GetDtoOptions(AuthorizationContext, request);
-
- if (!request.UserId.Equals(Guid.Empty))
- {
- var user = UserManager.GetUserById(request.UserId);
-
- return DtoService.GetBaseItemDto(item, dtoOptions, user);
- }
-
- return DtoService.GetBaseItemDto(item, dtoOptions);
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetYears request)
- {
- var result = GetResult(request);
-
- return ToOptimizedResult(result);
- }
-
- /// <summary>
- /// Gets all items.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="items">The items.</param>
- /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
- protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items)
- {
- return items
- .Select(i => i.ProductionYear ?? 0)
- .Where(i => i > 0)
- .Distinct()
- .Select(year => LibraryManager.GetYear(year));
- }
- }
-}
diff --git a/MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs b/MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs
new file mode 100644
index 000000000..d18fd95d5
--- /dev/null
+++ b/MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Buffers;
+using System.Buffers.Text;
+using System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace MediaBrowser.Common.Json.Converters
+{
+ /// <summary>
+ /// Long to String JSON converter.
+ /// Javascript does not support 64-bit integers.
+ /// </summary>
+ public class JsonInt64Converter : JsonConverter<long>
+ {
+ /// <summary>
+ /// Read JSON string as int64.
+ /// </summary>
+ /// <param name="reader"><see cref="Utf8JsonReader"/>.</param>
+ /// <param name="type">Type.</param>
+ /// <param name="options">Options.</param>
+ /// <returns>Parsed value.</returns>
+ public override long Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
+ {
+ if (reader.TokenType == JsonTokenType.String)
+ {
+ // try to parse number directly from bytes
+ var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
+ if (Utf8Parser.TryParse(span, out long number, out var bytesConsumed) && span.Length == bytesConsumed)
+ {
+ return number;
+ }
+
+ // try to parse from a string if the above failed, this covers cases with other escaped/UTF characters
+ if (long.TryParse(reader.GetString(), out number))
+ {
+ return number;
+ }
+ }
+
+ // fallback to default handling
+ return reader.GetInt64();
+ }
+
+ /// <summary>
+ /// Write long to JSON string.
+ /// </summary>
+ /// <param name="writer"><see cref="Utf8JsonWriter"/>.</param>
+ /// <param name="value">Value to write.</param>
+ /// <param name="options">Options.</param>
+ public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(value.ToString(NumberFormatInfo.InvariantInfo));
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs
index ec3c45476..13f2f060b 100644
--- a/MediaBrowser.Common/Json/JsonDefaults.cs
+++ b/MediaBrowser.Common/Json/JsonDefaults.cs
@@ -31,6 +31,7 @@ namespace MediaBrowser.Common.Json
options.Converters.Add(new JsonInt32Converter());
options.Converters.Add(new JsonStringEnumConverter());
options.Converters.Add(new JsonNonStringKeyDictionaryConverterFactory());
+ options.Converters.Add(new JsonInt64Converter());
return options;
}
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index c9ca153c7..7380f39fd 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -17,8 +17,8 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.5" />
- <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.5" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.6" />
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.6" />
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" />
</ItemGroup>
diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs
index 3ba75abd8..a0330afef 100644
--- a/MediaBrowser.Common/Net/INetworkManager.cs
+++ b/MediaBrowser.Common/Net/INetworkManager.cs
@@ -11,14 +11,21 @@ namespace MediaBrowser.Common.Net
{
event EventHandler NetworkChanged;
+ /// <summary>
+ /// Gets or sets a function to return the list of user defined LAN addresses.
+ /// </summary>
Func<string[]> LocalSubnetsFn { get; set; }
/// <summary>
- /// Gets a random port number that is currently available.
+ /// Gets a random port TCP number that is currently available.
/// </summary>
/// <returns>System.Int32.</returns>
int GetRandomUnusedTcpPort();
+ /// <summary>
+ /// Gets a random port UDP number that is currently available.
+ /// </summary>
+ /// <returns>System.Int32.</returns>
int GetRandomUnusedUdpPort();
/// <summary>
@@ -35,18 +42,56 @@ namespace MediaBrowser.Common.Net
bool IsInPrivateAddressSpace(string endpoint);
/// <summary>
+ /// Determines whether [is in private address space 10.x.x.x] [the specified endpoint] and exists in the subnets returned by GetSubnets().
+ /// </summary>
+ /// <param name="endpoint">The endpoint.</param>
+ /// <returns><c>true</c> if [is in private address space 10.x.x.x] [the specified endpoint]; otherwise, <c>false</c>.</returns>
+ bool IsInPrivateAddressSpaceAndLocalSubnet(string endpoint);
+
+ /// <summary>
/// Determines whether [is in local network] [the specified endpoint].
/// </summary>
/// <param name="endpoint">The endpoint.</param>
/// <returns><c>true</c> if [is in local network] [the specified endpoint]; otherwise, <c>false</c>.</returns>
bool IsInLocalNetwork(string endpoint);
- IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface);
+ /// <summary>
+ /// Investigates an caches a list of interface addresses, excluding local link and LAN excluded addresses.
+ /// </summary>
+ /// <returns>The list of ipaddresses.</returns>
+ IPAddress[] GetLocalIpAddresses();
+ /// <summary>
+ /// Checks if the given address falls within the ranges given in [subnets]. The addresses in subnets can be hosts or subnets in the CIDR format.
+ /// </summary>
+ /// <param name="addressString">The address to check.</param>
+ /// <param name="subnets">If true, check against addresses in the LAN settings surrounded by brackets ([]).</param>
+ /// <returns><c>true</c>if the address is in at least one of the given subnets, <c>false</c> otherwise.</returns>
bool IsAddressInSubnets(string addressString, string[] subnets);
+ /// <summary>
+ /// Returns true if address is in the LAN list in the config file.
+ /// </summary>
+ /// <param name="address">The address to check.</param>
+ /// <param name="excludeInterfaces">If true, check against addresses in the LAN settings which have [] arroud and return true if it matches the address give in address.</param>
+ /// <param name="excludeRFC">If true, returns false if address is in the 127.x.x.x or 169.128.x.x range.</param>
+ /// <returns><c>false</c>if the address isn't in the LAN list, <c>true</c> if the address has been defined as a LAN address.</returns>
+ bool IsAddressInSubnets(IPAddress address, bool excludeInterfaces, bool excludeRFC);
+
+ /// <summary>
+ /// Checks if address is in the LAN list in the config file.
+ /// </summary>
+ /// <param name="address1">Source address to check.</param>
+ /// <param name="address2">Destination address to check against.</param>
+ /// <param name="subnetMask">Destination subnet to check against.</param>
+ /// <returns><c>true/false</c>depending on whether address1 is in the same subnet as IPAddress2 with subnetMask.</returns>
bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask);
+ /// <summary>
+ /// Returns the subnet mask of an interface with the given address.
+ /// </summary>
+ /// <param name="address">The address to check.</param>
+ /// <returns>Returns the subnet mask of an interface with the given address, or null if an interface match cannot be found.</returns>
IPAddress GetLocalIpSubnetMask(IPAddress address);
}
}
diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs
index 9e4a360c3..f10a1918f 100644
--- a/MediaBrowser.Common/Plugins/BasePlugin.cs
+++ b/MediaBrowser.Common/Plugins/BasePlugin.cs
@@ -2,6 +2,7 @@
using System;
using System.IO;
+using System.Reflection;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization;
@@ -50,6 +51,12 @@ namespace MediaBrowser.Common.Plugins
public string DataFolderPath { get; private set; }
/// <summary>
+ /// Gets a value indicating whether the plugin can be uninstalled.
+ /// </summary>
+ public bool CanUninstall => !Path.GetDirectoryName(AssemblyFilePath)
+ .Equals(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), StringComparison.InvariantCulture);
+
+ /// <summary>
/// Gets the plugin info.
/// </summary>
/// <returns>PluginInfo.</returns>
@@ -60,7 +67,8 @@ namespace MediaBrowser.Common.Plugins
Name = Name,
Version = Version.ToString(),
Description = Description,
- Id = Id.ToString()
+ Id = Id.ToString(),
+ CanUninstall = CanUninstall
};
return info;
diff --git a/MediaBrowser.Common/Plugins/IPlugin.cs b/MediaBrowser.Common/Plugins/IPlugin.cs
index d34820961..7bd37d210 100644
--- a/MediaBrowser.Common/Plugins/IPlugin.cs
+++ b/MediaBrowser.Common/Plugins/IPlugin.cs
@@ -41,6 +41,11 @@ namespace MediaBrowser.Common.Plugins
string AssemblyFilePath { get; }
/// <summary>
+ /// Gets a value indicating whether the plugin can be uninstalled.
+ /// </summary>
+ bool CanUninstall { get; }
+
+ /// <summary>
/// Gets the full path to the data folder, where the plugin can store any miscellaneous files needed.
/// </summary>
/// <value>The data folder path.</value>
diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs
index 965ffe0ec..4b4030bc2 100644
--- a/MediaBrowser.Common/Updates/IInstallationManager.cs
+++ b/MediaBrowser.Common/Updates/IInstallationManager.cs
@@ -5,7 +5,6 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Plugins;
-using MediaBrowser.Model.Events;
using MediaBrowser.Model.Updates;
namespace MediaBrowser.Common.Updates
@@ -41,6 +40,14 @@ namespace MediaBrowser.Common.Updates
IEnumerable<InstallationInfo> CompletedInstallations { get; }
/// <summary>
+ /// Parses a plugin manifest at the supplied URL.
+ /// </summary>
+ /// <param name="manifest">The URL to query.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{IReadOnlyList{PackageInfo}}.</returns>
+ Task<IReadOnlyList<PackageInfo>> GetPackages(string manifest, CancellationToken cancellationToken = default);
+
+ /// <summary>
/// Gets all available packages.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
diff --git a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs
index c0324a384..15c902777 100644
--- a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs
+++ b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs
@@ -7,12 +7,12 @@ namespace MediaBrowser.Controller.Authentication
public interface IAuthenticationProvider
{
string Name { get; }
+
bool IsEnabled { get; }
+
Task<ProviderAuthenticationResult> Authenticate(string username, string password);
bool HasPassword(User user);
Task ChangePassword(User user, string newPassword);
- void ChangeEasyPassword(User user, string newPassword, string newPasswordHash);
- string GetEasyPasswordHash(User user);
}
public interface IRequiresResolvedUser
@@ -28,6 +28,7 @@ namespace MediaBrowser.Controller.Authentication
public class ProviderAuthenticationResult
{
public string Username { get; set; }
+
public string DisplayName { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs
index d9b814f69..693df80ac 100644
--- a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs
+++ b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs
@@ -8,7 +8,9 @@ namespace MediaBrowser.Controller.Authentication
public interface IPasswordResetProvider
{
string Name { get; }
+
bool IsEnabled { get; }
+
Task<ForgotPasswordResult> StartForgotPasswordProcess(User user, bool isInNetwork);
Task<PinRedeemResult> RedeemPasswordResetPin(string pin);
}
@@ -16,6 +18,7 @@ namespace MediaBrowser.Controller.Authentication
public class PasswordPinCreationResult
{
public string PinFile { get; set; }
+
public DateTime ExpirationDate { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs
index aff68883b..00d4d9cb3 100644
--- a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs
+++ b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs
@@ -24,7 +24,9 @@ namespace MediaBrowser.Controller.Channels
public string Overview { get; set; }
public List<string> Genres { get; set; }
+
public List<string> Studios { get; set; }
+
public List<string> Tags { get; set; }
public List<PersonInfo> People { get; set; }
@@ -34,26 +36,33 @@ namespace MediaBrowser.Controller.Channels
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<TrailerType> TrailerTypes { get; set; }
public Dictionary<string, string> 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<MediaSourceInfo> MediaSources { get; set; }
@@ -63,7 +72,9 @@ namespace MediaBrowser.Controller.Channels
public List<string> Artists { get; set; }
public List<string> AlbumArtists { get; set; }
+
public bool IsLiveStream { get; set; }
+
public string Etag { get; set; }
public ChannelItemInfo()
diff --git a/MediaBrowser.Controller/Channels/ISearchableChannel.cs b/MediaBrowser.Controller/Channels/ISearchableChannel.cs
index d5b76a160..48043ad7a 100644
--- a/MediaBrowser.Controller/Channels/ISearchableChannel.cs
+++ b/MediaBrowser.Controller/Channels/ISearchableChannel.cs
@@ -35,12 +35,10 @@ namespace MediaBrowser.Controller.Channels
public interface IDisableMediaSourceDisplay
{
-
}
public interface ISupportsMediaProbe
{
-
}
public interface IHasFolderAttributes
diff --git a/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs b/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs
index 60455e68a..1f4a26064 100644
--- a/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs
+++ b/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs
@@ -18,7 +18,7 @@ namespace MediaBrowser.Controller.Channels
public List<ChannelMediaContentType> ContentTypes { get; set; }
/// <summary>
- /// Represents the maximum number of records the channel allows retrieving at a time
+ /// Represents the maximum number of records the channel allows retrieving at a time.
/// </summary>
public int? MaxPageSize { get; set; }
diff --git a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs
index 51fe4ce29..1e7549d2b 100644
--- a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs
+++ b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs
@@ -15,6 +15,7 @@ namespace MediaBrowser.Controller.Collections
public Dictionary<string, string> ProviderIds { get; set; }
public string[] ItemIdList { get; set; }
+
public Guid[] UserIds { get; set; }
public CollectionCreationOptions()
diff --git a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs
index 6660743e6..43ad04dba 100644
--- a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs
+++ b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs
@@ -4,7 +4,7 @@ using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Controller.Configuration
{
/// <summary>
- /// Interface IServerConfigurationManager
+ /// Interface IServerConfigurationManager.
/// </summary>
public interface IServerConfigurationManager : IConfigurationManager
{
@@ -19,7 +19,5 @@ namespace MediaBrowser.Controller.Configuration
/// </summary>
/// <value>The configuration.</value>
ServerConfiguration Configuration { get; }
-
- bool SetOptimalValues();
}
}
diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
index f1873d539..69d799165 100644
--- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs
+++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
@@ -10,7 +10,7 @@ using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Drawing
{
/// <summary>
- /// Interface IImageProcessor
+ /// Interface IImageProcessor.
/// </summary>
public interface IImageProcessor
{
@@ -30,7 +30,7 @@ namespace MediaBrowser.Controller.Drawing
/// Gets the dimensions of the image.
/// </summary>
/// <param name="path">Path to the image file.</param>
- /// <returns>ImageDimensions</returns>
+ /// <returns>ImageDimensions.</returns>
ImageDimensions GetImageDimensions(string path);
/// <summary>
@@ -38,14 +38,14 @@ namespace MediaBrowser.Controller.Drawing
/// </summary>
/// <param name="item">The base item.</param>
/// <param name="info">The information.</param>
- /// <returns>ImageDimensions</returns>
+ /// <returns>ImageDimensions.</returns>
ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info);
/// <summary>
/// Gets the blurhash of the image.
/// </summary>
/// <param name="path">Path to the image file.</param>
- /// <returns>BlurHash</returns>
+ /// <returns>BlurHash.</returns>
string GetImageBlurHash(string path);
/// <summary>
diff --git a/MediaBrowser.Controller/Drawing/ImageHelper.cs b/MediaBrowser.Controller/Drawing/ImageHelper.cs
index c87a248b5..e1273fe7f 100644
--- a/MediaBrowser.Controller/Drawing/ImageHelper.cs
+++ b/MediaBrowser.Controller/Drawing/ImageHelper.cs
@@ -16,6 +16,7 @@ namespace MediaBrowser.Controller.Drawing
return newSize;
}
+
return GetSizeEstimate(options);
}
diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
index 870e0278e..31d2c1bd4 100644
--- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
+++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
@@ -15,6 +15,7 @@ namespace MediaBrowser.Controller.Drawing
}
public Guid ItemId { get; set; }
+
public BaseItem Item { get; set; }
public ItemImageInfo Image { get; set; }
@@ -38,12 +39,15 @@ namespace MediaBrowser.Controller.Drawing
public bool AddPlayedIndicator { 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)
@@ -73,14 +77,17 @@ namespace MediaBrowser.Controller.Drawing
{
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;
diff --git a/MediaBrowser.Controller/Dto/DtoOptions.cs b/MediaBrowser.Controller/Dto/DtoOptions.cs
index cdaf95f5c..cf301f1e4 100644
--- a/MediaBrowser.Controller/Dto/DtoOptions.cs
+++ b/MediaBrowser.Controller/Dto/DtoOptions.cs
@@ -14,11 +14,17 @@ namespace MediaBrowser.Controller.Dto
};
public ItemFields[] Fields { get; set; }
+
public ImageType[] 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 DtoOptions()
diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs
index 56e6c47c4..0dadc283e 100644
--- a/MediaBrowser.Controller/Dto/IDtoService.cs
+++ b/MediaBrowser.Controller/Dto/IDtoService.cs
@@ -7,7 +7,7 @@ using MediaBrowser.Model.Querying;
namespace MediaBrowser.Controller.Dto
{
/// <summary>
- /// Interface IDtoService
+ /// Interface IDtoService.
/// </summary>
public interface IDtoService
{
diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs
index 54540e892..e1c800e61 100644
--- a/MediaBrowser.Controller/Entities/AggregateFolder.cs
+++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs
@@ -35,7 +35,7 @@ namespace MediaBrowser.Controller.Entities
public override bool SupportsPlayedStatus => false;
/// <summary>
- /// The _virtual children
+ /// The _virtual children.
/// </summary>
private readonly ConcurrentBag<BaseItem> _virtualChildren = new ConcurrentBag<BaseItem>();
@@ -195,6 +195,7 @@ namespace MediaBrowser.Controller.Entities
return child;
}
}
+
return null;
}
}
diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs
index a8ea2157d..98f802b5d 100644
--- a/MediaBrowser.Controller/Entities/Audio/Audio.cs
+++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs
@@ -11,7 +11,7 @@ using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Entities.Audio
{
/// <summary>
- /// Class Audio
+ /// Class Audio.
/// </summary>
public class Audio : BaseItem,
IHasAlbumArtist,
@@ -93,6 +93,7 @@ namespace MediaBrowser.Controller.Entities.Audio
{
songKey = ParentIndexNumber.Value.ToString("0000") + "-" + songKey;
}
+
songKey += Name;
if (!string.IsNullOrEmpty(Album))
@@ -117,6 +118,7 @@ namespace MediaBrowser.Controller.Entities.Audio
{
return UnratedItem.Music;
}
+
return base.GetBlockUnratedType();
}
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
index f7b2f9549..5a1ddeece 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
@@ -15,7 +15,7 @@ using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider;
namespace MediaBrowser.Controller.Entities.Audio
{
/// <summary>
- /// Class MusicAlbum
+ /// Class MusicAlbum.
/// </summary>
public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<AlbumInfo>, IMetadataContainer
{
@@ -56,6 +56,7 @@ namespace MediaBrowser.Controller.Entities.Audio
{
return LibraryManager.GetArtist(name, options);
}
+
return null;
}
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
index 63db3cfab..ea5c41caa 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
@@ -15,7 +15,7 @@ using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider;
namespace MediaBrowser.Controller.Entities.Audio
{
/// <summary>
- /// Class MusicArtist
+ /// Class MusicArtist.
/// </summary>
public class MusicArtist : Folder, IItemByName, IHasMusicGenres, IHasDualAccess, IHasLookupInfo<ArtistInfo>
{
@@ -111,7 +111,7 @@ namespace MediaBrowser.Controller.Entities.Audio
/// <summary>
/// Returns the folder containing the item.
- /// If the item is a folder, it returns the folder itself
+ /// If the item is a folder, it returns the folder itself.
/// </summary>
/// <value>The containing folder path.</value>
[JsonIgnore]
@@ -135,6 +135,7 @@ namespace MediaBrowser.Controller.Entities.Audio
list.Add("Artist-" + (item.Name ?? string.Empty).RemoveDiacritics());
return list;
}
+
public override string CreatePresentationUniqueKey()
{
return "Artist-" + (Name ?? string.Empty).RemoveDiacritics();
@@ -201,7 +202,7 @@ namespace MediaBrowser.Controller.Entities.Audio
}
/// <summary>
- /// This is called before any metadata refresh and returns true or false indicating if changes were made
+ /// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary>
public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
{
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
index 537e9630b..4f6aa0776 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
@@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities.Audio
{
/// <summary>
- /// Class MusicGenre
+ /// Class MusicGenre.
/// </summary>
public class MusicGenre : BaseItem, IItemByName
{
@@ -18,6 +18,7 @@ namespace MediaBrowser.Controller.Entities.Audio
list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
return list;
}
+
public override string CreatePresentationUniqueKey()
{
return GetUserDataKeys()[0];
@@ -34,7 +35,7 @@ namespace MediaBrowser.Controller.Entities.Audio
/// <summary>
/// Returns the folder containing the item.
- /// If the item is a folder, it returns the folder itself
+ /// If the item is a folder, it returns the folder itself.
/// </summary>
/// <value>The containing folder path.</value>
[JsonIgnore]
@@ -94,11 +95,12 @@ namespace MediaBrowser.Controller.Entities.Audio
Logger.LogDebug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
return true;
}
+
return base.RequiresRefresh();
}
/// <summary>
- /// This is called before any metadata refresh and returns true or false indicating if changes were made
+ /// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary>
public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
{
diff --git a/MediaBrowser.Controller/Entities/AudioBook.cs b/MediaBrowser.Controller/Entities/AudioBook.cs
index 4adaf4c6e..11ff8a257 100644
--- a/MediaBrowser.Controller/Entities/AudioBook.cs
+++ b/MediaBrowser.Controller/Entities/AudioBook.cs
@@ -24,10 +24,12 @@ namespace MediaBrowser.Controller.Entities
{
return SeriesName;
}
+
public string FindSeriesName()
{
return SeriesName;
}
+
public string FindSeriesPresentationUniqueKey()
{
return SeriesPresentationUniqueKey;
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 0eabb4b7a..90f62e153 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -31,12 +31,12 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities
{
/// <summary>
- /// Class BaseItem
+ /// Class BaseItem.
/// </summary>
public abstract class BaseItem : IHasProviderIds, IHasLookupInfo<ItemLookupInfo>, IEquatable<BaseItem>
{
/// <summary>
- /// The supported image extensions
+ /// The supported image extensions.
/// </summary>
public static readonly string[] SupportedImageExtensions
= new[] { ".png", ".jpg", ".jpeg", ".tbn", ".gif" };
@@ -75,7 +75,7 @@ namespace MediaBrowser.Controller.Entities
public static char SlugChar = '-';
/// <summary>
- /// The trailer folder name
+ /// The trailer folder name.
/// </summary>
public const string TrailerFolderName = "trailers";
public const string ThemeSongsFolderName = "theme-music";
@@ -108,6 +108,7 @@ namespace MediaBrowser.Controller.Entities
public string PreferredMetadataLanguage { get; set; }
public long? Size { get; set; }
+
public string Container { get; set; }
[JsonIgnore]
@@ -243,7 +244,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Returns the folder containing the item.
- /// If the item is a folder, it returns the folder itself
+ /// If the item is a folder, it returns the folder itself.
/// </summary>
[JsonIgnore]
public virtual string ContainingFolderPath
@@ -267,7 +268,7 @@ namespace MediaBrowser.Controller.Entities
public string ServiceName { get; set; }
/// <summary>
- /// If this content came from an external service, the id of the content on that service
+ /// If this content came from an external service, the id of the content on that service.
/// </summary>
[JsonIgnore]
public string ExternalId { get; set; }
@@ -300,7 +301,7 @@ namespace MediaBrowser.Controller.Entities
{
get
{
- //if (IsOffline)
+ // if (IsOffline)
//{
// return LocationType.Offline;
//}
@@ -411,7 +412,7 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// This is just a helper for convenience
+ /// This is just a helper for convenience.
/// </summary>
/// <value>The primary image path.</value>
[JsonIgnore]
@@ -448,6 +449,7 @@ namespace MediaBrowser.Controller.Entities
// hack alert
return true;
}
+
if (SourceType == SourceType.Channel)
{
// hack alert
@@ -556,18 +558,26 @@ namespace MediaBrowser.Controller.Entities
public DateTime DateLastRefreshed { get; set; }
/// <summary>
- /// The logger
+ /// The logger.
/// </summary>
- public static ILoggerFactory LoggerFactory { get; set; }
public static ILogger<BaseItem> Logger { get; set; }
+
public static ILibraryManager LibraryManager { get; set; }
+
public static IServerConfigurationManager ConfigurationManager { get; set; }
+
public static IProviderManager ProviderManager { get; set; }
+
public static ILocalizationManager LocalizationManager { get; set; }
+
public static IItemRepository ItemRepository { get; set; }
+
public static IFileSystem FileSystem { get; set; }
+
public static IUserDataManager UserDataManager { get; set; }
+
public static IChannelManager ChannelManager { get; set; }
+
public static IMediaSourceManager MediaSourceManager { get; set; }
/// <summary>
@@ -603,7 +613,7 @@ namespace MediaBrowser.Controller.Entities
{
if (!IsFileProtocol)
{
- return new string[] { };
+ return Array.Empty<string>();
}
return new[] { Path };
@@ -644,8 +654,10 @@ namespace MediaBrowser.Controller.Entities
_sortName = CreateSortName();
}
}
+
return _sortName;
}
+
set => _sortName = value;
}
@@ -676,7 +688,10 @@ namespace MediaBrowser.Controller.Entities
/// <returns>System.String.</returns>
protected virtual string CreateSortName()
{
- if (Name == null) return null; //some items may not have name filled in properly
+ if (Name == null)
+ {
+ return null; // some items may not have name filled in properly
+ }
if (!EnableAlphaNumericSorting)
{
@@ -736,7 +751,7 @@ namespace MediaBrowser.Controller.Entities
builder.Append(chunkBuilder);
}
- //logger.LogDebug("ModifySortChunks Start: {0} End: {1}", name, builder.ToString());
+ // logger.LogDebug("ModifySortChunks Start: {0} End: {1}", name, builder.ToString());
return builder.ToString().RemoveDiacritics();
}
@@ -767,7 +782,6 @@ namespace MediaBrowser.Controller.Entities
get => GetParent() as Folder;
set
{
-
}
}
@@ -800,7 +814,7 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// Finds a parent of a given type
+ /// Finds a parent of a given type.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>``0.</returns>
@@ -815,6 +829,7 @@ namespace MediaBrowser.Controller.Entities
return item;
}
}
+
return null;
}
@@ -838,6 +853,7 @@ namespace MediaBrowser.Controller.Entities
{
return null;
}
+
return LibraryManager.GetItemById(id);
}
}
@@ -1011,7 +1027,7 @@ namespace MediaBrowser.Controller.Entities
return PlayAccess.None;
}
- //if (!user.IsParentalScheduleAllowed())
+ // if (!user.IsParentalScheduleAllowed())
//{
// return PlayAccess.None;
//}
@@ -1064,7 +1080,6 @@ namespace MediaBrowser.Controller.Entities
}
return 1;
-
}).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0)
.ThenByDescending(i =>
{
@@ -1247,8 +1262,7 @@ namespace MediaBrowser.Controller.Entities
// Support plex/xbmc convention
files.AddRange(fileSystemChildren
- .Where(i => !i.IsDirectory && string.Equals(FileSystem.GetFileNameWithoutExtension(i), ThemeSongFilename, StringComparison.OrdinalIgnoreCase))
- );
+ .Where(i => !i.IsDirectory && string.Equals(FileSystem.GetFileNameWithoutExtension(i), ThemeSongFilename, StringComparison.OrdinalIgnoreCase)));
return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions())
.OfType<Audio.Audio>()
@@ -1347,20 +1361,18 @@ namespace MediaBrowser.Controller.Entities
protected virtual void TriggerOnRefreshStart()
{
-
}
protected virtual void TriggerOnRefreshComplete()
{
-
}
/// <summary>
- /// Overrides the base implementation to refresh metadata for local trailers
+ /// Overrides the base implementation to refresh metadata for local trailers.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>true if a provider reports we changed</returns>
+ /// <returns>true if a provider reports we changed.</returns>
public async Task<ItemUpdateType> RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
{
TriggerOnRefreshStart();
@@ -1758,7 +1770,7 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// Determines if a given user has access to this item
+ /// Determines if a given user has access to this item.
/// </summary>
/// <param name="user">The user.</param>
/// <returns><c>true</c> if [is parental allowed] [the specified user]; otherwise, <c>false</c>.</returns>
@@ -2064,7 +2076,7 @@ namespace MediaBrowser.Controller.Entities
public virtual bool EnableRememberingTrackSelections => true;
/// <summary>
- /// Adds a studio to the item
+ /// Adds a studio to the item.
/// </summary>
/// <param name="name">The name.</param>
/// <exception cref="ArgumentNullException"></exception>
@@ -2100,7 +2112,7 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// Adds a genre to the item
+ /// Adds a genre to the item.
/// </summary>
/// <param name="name">The name.</param>
/// <exception cref="ArgumentNullException"></exception>
@@ -2175,7 +2187,7 @@ namespace MediaBrowser.Controller.Entities
var data = UserDataManager.GetUserData(user, this);
- //I think it is okay to do this here.
+ // I think it is okay to do this here.
// if this is only called when a user is manually forcing something to un-played
// then it probably is what we want to do...
data.PlayCount = 0;
@@ -2195,7 +2207,7 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// Gets an image
+ /// Gets an image.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="imageIndex">Index of the image.</param>
@@ -2511,7 +2523,7 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// Gets the file system path to delete when the item is to be deleted
+ /// Gets the file system path to delete when the item is to be deleted.
/// </summary>
/// <returns></returns>
public virtual IEnumerable<FileSystemMetadata> GetDeletePaths()
@@ -2760,8 +2772,8 @@ namespace MediaBrowser.Controller.Entities
newOptions.ForceSave = true;
}
- //var parentId = Id;
- //if (!video.IsOwnedItem || video.ParentId != parentId)
+ // var parentId = Id;
+ // if (!video.IsOwnedItem || video.ParentId != parentId)
//{
// video.IsOwnedItem = true;
// video.ParentId = parentId;
@@ -2937,9 +2949,13 @@ namespace MediaBrowser.Controller.Entities
public IEnumerable<BaseItem> GetTrailers()
{
if (this is IHasTrailers)
+ {
return ((IHasTrailers)this).LocalTrailerIds.Select(LibraryManager.GetItemById).Where(i => i != null).OrderBy(i => i.SortName);
+ }
else
+ {
return Array.Empty<BaseItem>();
+ }
}
public virtual bool IsHD => Height >= 720;
diff --git a/MediaBrowser.Controller/Entities/BasePluginFolder.cs b/MediaBrowser.Controller/Entities/BasePluginFolder.cs
index 62d172fcc..106385bc6 100644
--- a/MediaBrowser.Controller/Entities/BasePluginFolder.cs
+++ b/MediaBrowser.Controller/Entities/BasePluginFolder.cs
@@ -27,7 +27,7 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public override bool SupportsPeople => false;
- //public override double? GetDefaultPrimaryImageAspectRatio()
+ // public override double? GetDefaultPrimaryImageAspectRatio()
//{
// double value = 16;
// value /= 9;
diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs
index e5adf88d1..5c6a9d2a2 100644
--- a/MediaBrowser.Controller/Entities/CollectionFolder.cs
+++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs
@@ -18,12 +18,14 @@ namespace MediaBrowser.Controller.Entities
{
/// <summary>
/// Specialized Folder class that points to a subset of the physical folders in the system.
- /// It is created from the user-specific folders within the system root
+ /// It is created from the user-specific folders within the system root.
/// </summary>
public class CollectionFolder : Folder, ICollectionFolder
{
public static IXmlSerializer XmlSerializer { get; set; }
+
public static IJsonSerializer JsonSerializer { get; set; }
+
public static IServerApplicationHost ApplicationHost { get; set; }
public CollectionFolder()
@@ -140,7 +142,7 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// Allow different display preferences for each collection folder
+ /// Allow different display preferences for each collection folder.
/// </summary>
/// <value>The display prefs id.</value>
[JsonIgnore]
@@ -155,6 +157,7 @@ namespace MediaBrowser.Controller.Entities
}
public string[] PhysicalLocationsList { get; set; }
+
public Guid[] PhysicalFolderIds { get; set; }
protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService)
@@ -222,7 +225,7 @@ namespace MediaBrowser.Controller.Entities
return null;
}
- return (totalProgresses / foldersWithProgress);
+ return totalProgresses / foldersWithProgress;
}
protected override bool RefreshLinkedChildren(IEnumerable<FileSystemMetadata> fileSystemChildren)
diff --git a/MediaBrowser.Controller/Entities/Extensions.cs b/MediaBrowser.Controller/Entities/Extensions.cs
index d2ca11740..3a34c668c 100644
--- a/MediaBrowser.Controller/Entities/Extensions.cs
+++ b/MediaBrowser.Controller/Entities/Extensions.cs
@@ -6,7 +6,7 @@ using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Entities
{
/// <summary>
- /// Class Extensions
+ /// Class Extensions.
/// </summary>
public static class Extensions
{
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 4af74f9cd..6441340f9 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -31,7 +31,7 @@ using Series = MediaBrowser.Controller.Entities.TV.Series;
namespace MediaBrowser.Controller.Entities
{
/// <summary>
- /// Class Folder
+ /// Class Folder.
/// </summary>
public class Folder : BaseItem
{
@@ -126,10 +126,12 @@ namespace MediaBrowser.Controller.Entities
{
return false;
}
+
if (this is UserView)
{
return false;
}
+
return true;
}
@@ -156,6 +158,7 @@ namespace MediaBrowser.Controller.Entities
{
item.DateCreated = DateTime.UtcNow;
}
+
if (item.DateModified == DateTime.MinValue)
{
item.DateModified = DateTime.UtcNow;
@@ -172,7 +175,7 @@ namespace MediaBrowser.Controller.Entities
public virtual IEnumerable<BaseItem> Children => LoadChildren();
/// <summary>
- /// thread-safe access to all recursive children of this folder - without regard to user
+ /// thread-safe access to all recursive children of this folder - without regard to user.
/// </summary>
/// <value>The recursive children.</value>
[JsonIgnore]
@@ -213,8 +216,8 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
protected virtual List<BaseItem> LoadChildren()
{
- //logger.LogDebug("Loading children from {0} {1} {2}", GetType().Name, Id, Path);
- //just load our children from the repo - the library will be validated and maintained in other processes
+ // logger.LogDebug("Loading children from {0} {1} {2}", GetType().Name, Id, Path);
+ // just load our children from the repo - the library will be validated and maintained in other processes
return GetCachedChildren();
}
@@ -229,7 +232,7 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// Validates that the children of the folder still exist
+ /// Validates that the children of the folder still exist.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
@@ -477,7 +480,7 @@ namespace MediaBrowser.Controller.Entities
innerProgress.RegisterAction(p =>
{
double innerPercent = currentInnerPercent;
- innerPercent += p / (count);
+ innerPercent += p / count;
progress.Report(innerPercent);
});
@@ -500,8 +503,8 @@ namespace MediaBrowser.Controller.Entities
if (series != null)
{
await series.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
-
}
+
await container.RefreshAllMetadata(refreshOptions, progress, cancellationToken).ConfigureAwait(false);
}
@@ -553,7 +556,7 @@ namespace MediaBrowser.Controller.Entities
innerProgress.RegisterAction(p =>
{
double innerPercent = currentInnerPercent;
- innerPercent += p / (count);
+ innerPercent += p / count;
progress.Report(innerPercent);
});
@@ -571,7 +574,7 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// Get the children of this folder from the actual file system
+ /// Get the children of this folder from the actual file system.
/// </summary>
/// <returns>IEnumerable{BaseItem}.</returns>
protected virtual IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
@@ -583,7 +586,7 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// Get our children from the repo - stubbed for now
+ /// Get our children from the repo - stubbed for now.
/// </summary>
/// <returns>IEnumerable{BaseItem}.</returns>
protected List<BaseItem> GetCachedChildren()
@@ -615,7 +618,6 @@ namespace MediaBrowser.Controller.Entities
{
EnableImages = false
}
-
});
return result.TotalRecordCount;
@@ -941,6 +943,7 @@ namespace MediaBrowser.Controller.Entities
{
items = items.Where(i => string.Compare(query.NameStartsWithOrGreater, i.SortName, StringComparison.CurrentCultureIgnoreCase) < 1);
}
+
if (!string.IsNullOrEmpty(query.NameStartsWith))
{
items = items.Where(i => i.SortName.StartsWith(query.NameStartsWith, StringComparison.OrdinalIgnoreCase));
@@ -991,18 +994,22 @@ namespace MediaBrowser.Controller.Entities
{
return false;
}
+
if (queryParent is Series)
{
return false;
}
+
if (queryParent is Season)
{
return false;
}
+
if (queryParent is MusicAlbum)
{
return false;
}
+
if (queryParent is MusicArtist)
{
return false;
@@ -1032,22 +1039,27 @@ namespace MediaBrowser.Controller.Entities
{
return false;
}
+
if (request.IsFavoriteOrLiked.HasValue)
{
return false;
}
+
if (request.IsLiked.HasValue)
{
return false;
}
+
if (request.IsPlayed.HasValue)
{
return false;
}
+
if (request.IsResumable.HasValue)
{
return false;
}
+
if (request.IsFolder.HasValue)
{
return false;
@@ -1223,7 +1235,7 @@ namespace MediaBrowser.Controller.Entities
throw new ArgumentNullException(nameof(user));
}
- //the true root should return our users root folder children
+ // the true root should return our users root folder children
if (IsPhysicalRoot)
{
return LibraryManager.GetUserRootFolder().GetChildren(user, includeLinkedChildren);
@@ -1288,7 +1300,7 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// Gets allowed recursive children of an item
+ /// Gets allowed recursive children of an item.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
@@ -1393,6 +1405,7 @@ namespace MediaBrowser.Controller.Entities
list.Add(child);
}
}
+
return list;
}
@@ -1415,6 +1428,7 @@ namespace MediaBrowser.Controller.Entities
return true;
}
}
+
return false;
}
@@ -1629,7 +1643,6 @@ namespace MediaBrowser.Controller.Entities
Recursive = true,
IsFolder = false,
EnableTotalRecordCount = false
-
});
// Sweep through recursively and update status
@@ -1647,7 +1660,6 @@ namespace MediaBrowser.Controller.Entities
IsFolder = false,
IsVirtualItem = false,
EnableTotalRecordCount = false
-
});
return itemsResult
@@ -1669,22 +1681,27 @@ namespace MediaBrowser.Controller.Entities
{
return false;
}
+
if (this is UserView)
{
return false;
}
+
if (this is UserRootFolder)
{
return false;
}
+
if (this is Channel)
{
return false;
}
+
if (SourceType != SourceType.Library)
{
return false;
}
+
var iItemByName = this as IItemByName;
if (iItemByName != null)
{
diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs
index 773c7df34..437def532 100644
--- a/MediaBrowser.Controller/Entities/Genre.cs
+++ b/MediaBrowser.Controller/Entities/Genre.cs
@@ -8,7 +8,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities
{
/// <summary>
- /// Class Genre
+ /// Class Genre.
/// </summary>
public class Genre : BaseItem, IItemByName
{
@@ -19,6 +19,7 @@ namespace MediaBrowser.Controller.Entities
list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
return list;
}
+
public override string CreatePresentationUniqueKey()
{
return GetUserDataKeys()[0];
@@ -31,7 +32,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Returns the folder containing the item.
- /// If the item is a folder, it returns the folder itself
+ /// If the item is a folder, it returns the folder itself.
/// </summary>
/// <value>The containing folder path.</value>
[JsonIgnore]
@@ -92,11 +93,12 @@ namespace MediaBrowser.Controller.Entities
Logger.LogDebug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
return true;
}
+
return base.RequiresRefresh();
}
/// <summary>
- /// This is called before any metadata refresh and returns true or false indicating if changes were made
+ /// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary>
public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
{
diff --git a/MediaBrowser.Controller/Entities/ICollectionFolder.cs b/MediaBrowser.Controller/Entities/ICollectionFolder.cs
index 4f0760746..245b23ff0 100644
--- a/MediaBrowser.Controller/Entities/ICollectionFolder.cs
+++ b/MediaBrowser.Controller/Entities/ICollectionFolder.cs
@@ -3,7 +3,7 @@ using System;
namespace MediaBrowser.Controller.Entities
{
/// <summary>
- /// This is just a marker interface to denote top level folders
+ /// This is just a marker interface to denote top level folders.
/// </summary>
public interface ICollectionFolder : IHasCollectionType
{
diff --git a/MediaBrowser.Controller/Entities/IHasAspectRatio.cs b/MediaBrowser.Controller/Entities/IHasAspectRatio.cs
index 149c1e5ab..d7d007668 100644
--- a/MediaBrowser.Controller/Entities/IHasAspectRatio.cs
+++ b/MediaBrowser.Controller/Entities/IHasAspectRatio.cs
@@ -1,7 +1,7 @@
namespace MediaBrowser.Controller.Entities
{
/// <summary>
- /// Interface IHasAspectRatio
+ /// Interface IHasAspectRatio.
/// </summary>
public interface IHasAspectRatio
{
diff --git a/MediaBrowser.Controller/Entities/IHasDisplayOrder.cs b/MediaBrowser.Controller/Entities/IHasDisplayOrder.cs
index abee75a28..13226b234 100644
--- a/MediaBrowser.Controller/Entities/IHasDisplayOrder.cs
+++ b/MediaBrowser.Controller/Entities/IHasDisplayOrder.cs
@@ -1,7 +1,7 @@
namespace MediaBrowser.Controller.Entities
{
/// <summary>
- /// Interface IHasDisplayOrder
+ /// Interface IHasDisplayOrder.
/// </summary>
public interface IHasDisplayOrder
{
diff --git a/MediaBrowser.Controller/Entities/IHasMediaSources.cs b/MediaBrowser.Controller/Entities/IHasMediaSources.cs
index 4635b9062..213c0a794 100644
--- a/MediaBrowser.Controller/Entities/IHasMediaSources.cs
+++ b/MediaBrowser.Controller/Entities/IHasMediaSources.cs
@@ -13,7 +13,9 @@ namespace MediaBrowser.Controller.Entities
List<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution);
List<MediaStream> GetMediaStreams();
Guid Id { get; set; }
+
long? RunTimeTicks { get; set; }
+
string Path { get; }
}
}
diff --git a/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs b/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs
index 777b40828..fd1c19c97 100644
--- a/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs
+++ b/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs
@@ -5,13 +5,21 @@ namespace MediaBrowser.Controller.Entities
public interface IHasProgramAttributes
{
bool IsMovie { get; set; }
+
bool IsSports { get; }
+
bool IsNews { get; }
+
bool IsKids { get; }
+
bool IsRepeat { get; set; }
+
bool IsSeries { get; set; }
+
ProgramAudio? Audio { get; set; }
+
string EpisodeTitle { get; set; }
+
string ServiceName { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Entities/IHasScreenshots.cs b/MediaBrowser.Controller/Entities/IHasScreenshots.cs
index 0975242f5..b027a0cb1 100644
--- a/MediaBrowser.Controller/Entities/IHasScreenshots.cs
+++ b/MediaBrowser.Controller/Entities/IHasScreenshots.cs
@@ -1,7 +1,7 @@
namespace MediaBrowser.Controller.Entities
{
/// <summary>
- /// Interface IHasScreenshots
+ /// Interface IHasScreenshots.
/// </summary>
public interface IHasScreenshots
{
diff --git a/MediaBrowser.Controller/Entities/IHasSeries.cs b/MediaBrowser.Controller/Entities/IHasSeries.cs
index 7da53f730..475a2ab85 100644
--- a/MediaBrowser.Controller/Entities/IHasSeries.cs
+++ b/MediaBrowser.Controller/Entities/IHasSeries.cs
@@ -9,11 +9,14 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
/// <value>The name of the series.</value>
string SeriesName { get; set; }
+
string FindSeriesName();
string FindSeriesSortName();
Guid SeriesId { get; set; }
+
Guid FindSeriesId();
string SeriesPresentationUniqueKey { get; set; }
+
string FindSeriesPresentationUniqueKey();
}
}
diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
index 496bee857..466cda67c 100644
--- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
@@ -21,100 +21,167 @@ namespace MediaBrowser.Controller.Entities
public BaseItem SimilarTo { get; set; }
public bool? IsFolder { get; set; }
+
public bool? IsFavorite { get; set; }
+
public bool? IsFavoriteOrLiked { get; set; }
+
public bool? IsLiked { get; set; }
+
public bool? IsPlayed { get; set; }
+
public bool? IsResumable { get; set; }
+
public bool? IncludeItemsByName { get; set; }
public string[] MediaTypes { get; set; }
+
public string[] IncludeItemTypes { get; set; }
+
public string[] ExcludeItemTypes { get; set; }
+
public string[] ExcludeTags { get; set; }
+
public string[] ExcludeInheritedTags { get; set; }
+
public string[] Genres { get; set; }
public bool? IsSpecialSeason { get; set; }
+
public bool? IsMissing { get; set; }
+
public bool? IsUnaired { get; set; }
+
public bool? CollapseBoxSetItems { get; set; }
public string NameStartsWithOrGreater { get; set; }
+
public string NameStartsWith { get; set; }
+
public string NameLessThan { get; set; }
+
public string NameContains { get; set; }
+
public string MinSortName { get; set; }
public string PresentationUniqueKey { get; set; }
+
public string Path { get; set; }
+
public string Name { get; set; }
public string Person { get; set; }
+
public Guid[] PersonIds { get; set; }
+
public Guid[] ItemIds { get; set; }
+
public Guid[] ExcludeItemIds { get; set; }
+
public string AdjacentTo { get; set; }
+
public string[] PersonTypes { get; set; }
public bool? Is3D { get; set; }
+
public bool? IsHD { get; set; }
+
public bool? IsLocked { get; set; }
+
public bool? IsPlaceHolder { get; set; }
public bool? HasImdbId { get; set; }
+
public bool? HasOverview { get; set; }
+
public bool? HasTmdbId { get; set; }
+
public bool? HasOfficialRating { get; set; }
+
public bool? HasTvdbId { get; set; }
+
public bool? HasThemeSong { get; set; }
+
public bool? HasThemeVideo { get; set; }
+
public bool? HasSubtitles { get; set; }
+
public bool? HasSpecialFeature { get; set; }
+
public bool? HasTrailer { get; set; }
+
public bool? HasParentalRating { get; set; }
public Guid[] StudioIds { get; set; }
+
public Guid[] GenreIds { get; set; }
+
public ImageType[] ImageTypes { get; set; }
+
public VideoType[] VideoTypes { get; set; }
+
public UnratedItem[] BlockUnratedItems { get; set; }
+
public int[] Years { get; set; }
+
public string[] Tags { get; set; }
+
public string[] OfficialRatings { get; set; }
public DateTime? MinPremiereDate { get; set; }
+
public DateTime? MaxPremiereDate { get; set; }
+
public DateTime? MinStartDate { get; set; }
+
public DateTime? MaxStartDate { get; set; }
+
public DateTime? MinEndDate { get; set; }
+
public DateTime? MaxEndDate { get; set; }
+
public bool? IsAiring { get; set; }
public bool? IsMovie { get; set; }
+
public bool? IsSports { get; set; }
+
public bool? IsKids { get; set; }
+
public bool? IsNews { get; set; }
+
public bool? IsSeries { get; set; }
+
public int? MinIndexNumber { get; set; }
+
public int? AiredDuringSeason { get; set; }
+
public double? MinCriticRating { get; set; }
+
public double? MinCommunityRating { get; set; }
public Guid[] ChannelIds { get; set; }
public int? ParentIndexNumber { get; set; }
+
public int? ParentIndexNumberNotEquals { get; set; }
+
public int? IndexNumber { get; set; }
+
public int? MinParentalRating { get; set; }
+
public int? MaxParentalRating { get; set; }
public bool? HasDeadParentId { get; set; }
+
public bool? IsVirtualItem { get; set; }
public Guid ParentId { get; set; }
+
public string ParentType { get; set; }
+
public Guid[] AncestorIds { get; set; }
+
public Guid[] TopParentIds { get; set; }
public BaseItem Parent
@@ -135,41 +202,65 @@ namespace MediaBrowser.Controller.Entities
}
public string[] PresetViews { get; set; }
+
public TrailerType[] TrailerTypes { get; set; }
+
public SourceType[] SourceTypes { get; set; }
public SeriesStatus[] SeriesStatuses { get; set; }
+
public string ExternalSeriesId { get; set; }
+
public string ExternalId { get; set; }
public Guid[] AlbumIds { get; set; }
+
public Guid[] ArtistIds { get; set; }
+
public Guid[] ExcludeArtistIds { get; set; }
+
public string AncestorWithPresentationUniqueKey { get; set; }
+
public string SeriesPresentationUniqueKey { get; set; }
public bool GroupByPresentationUniqueKey { get; set; }
+
public bool GroupBySeriesPresentationUniqueKey { get; set; }
+
public bool EnableTotalRecordCount { get; set; }
+
public bool ForceDirect { get; set; }
+
public Dictionary<string, string> ExcludeProviderIds { get; set; }
+
public bool EnableGroupByMetadataKey { get; set; }
+
public bool? HasChapterImages { get; set; }
public IReadOnlyList<(string, SortOrder)> OrderBy { get; set; }
public DateTime? MinDateCreated { get; set; }
+
public DateTime? MinDateLastSaved { get; set; }
+
public DateTime? MinDateLastSavedForUser { get; set; }
public DtoOptions DtoOptions { get; set; }
+
public int MinSimilarityScore { get; set; }
+
public string HasNoAudioTrackWithLanguage { get; set; }
+
public string HasNoInternalSubtitleTrackWithLanguage { get; set; }
+
public string HasNoExternalSubtitleTrackWithLanguage { get; set; }
+
public string HasNoSubtitleTrackWithLanguage { get; set; }
+
public bool? IsDeadArtist { get; set; }
+
public bool? IsDeadStudio { get; set; }
+
public bool? IsDeadPerson { get; set; }
public InternalItemsQuery()
@@ -240,17 +331,29 @@ namespace MediaBrowser.Controller.Entities
}
public Dictionary<string, string> HasAnyProviderId { get; set; }
+
public Guid[] AlbumArtistIds { get; set; }
+
public Guid[] BoxSetLibraryFolders { get; set; }
+
public Guid[] ContributingArtistIds { get; set; }
+
public bool? HasAired { get; set; }
+
public bool? HasOwnerId { get; set; }
+
public bool? Is4K { get; set; }
+
public int? MaxHeight { get; set; }
+
public int? MaxWidth { get; set; }
+
public int? MinHeight { get; set; }
+
public int? MinWidth { get; set; }
+
public string SearchTerm { get; set; }
+
public string SeriesTimerId { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Entities/LinkedChild.cs b/MediaBrowser.Controller/Entities/LinkedChild.cs
index d88c31007..65753a26e 100644
--- a/MediaBrowser.Controller/Entities/LinkedChild.cs
+++ b/MediaBrowser.Controller/Entities/LinkedChild.cs
@@ -9,14 +9,16 @@ namespace MediaBrowser.Controller.Entities
public class LinkedChild
{
public string Path { get; set; }
+
public LinkedChildType Type { get; set; }
+
public string LibraryItemId { get; set; }
[JsonIgnore]
public string Id { get; set; }
/// <summary>
- /// Serves as a cache
+ /// Serves as a cache.
/// </summary>
public Guid? ItemId { get; set; }
@@ -63,6 +65,7 @@ namespace MediaBrowser.Controller.Entities
{
return _fileSystem.AreEqual(x.Path, y.Path);
}
+
return false;
}
diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
index be71bcc3c..c131c5430 100644
--- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
+++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
@@ -11,7 +11,7 @@ using MediaBrowser.Model.Querying;
namespace MediaBrowser.Controller.Entities.Movies
{
/// <summary>
- /// Class BoxSet
+ /// Class BoxSet.
/// </summary>
public class BoxSet : Folder, IHasTrailers, IHasDisplayOrder, IHasLookupInfo<BoxSetInfo>
{
@@ -198,7 +198,7 @@ namespace MediaBrowser.Controller.Entities.Movies
public Guid[] GetLibraryFolderIds()
{
- var expandedFolders = new List<Guid>() { };
+ var expandedFolders = new List<Guid>();
return FlattenItems(this, expandedFolders)
.SelectMany(i => LibraryManager.GetCollectionFolders(i))
diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs
index 26a165025..53badac4d 100644
--- a/MediaBrowser.Controller/Entities/Movies/Movie.cs
+++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs
@@ -13,7 +13,7 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Controller.Entities.Movies
{
/// <summary>
- /// Class Movie
+ /// Class Movie.
/// </summary>
public class Movie : Video, IHasSpecialFeatures, IHasTrailers, IHasLookupInfo<MovieInfo>, ISupportsBoxSetGrouping
{
diff --git a/MediaBrowser.Controller/Entities/PeopleHelper.cs b/MediaBrowser.Controller/Entities/PeopleHelper.cs
index 2fb613768..c39495759 100644
--- a/MediaBrowser.Controller/Entities/PeopleHelper.cs
+++ b/MediaBrowser.Controller/Entities/PeopleHelper.cs
@@ -113,6 +113,7 @@ namespace MediaBrowser.Controller.Entities
return true;
}
}
+
return false;
}
}
diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs
index 9e4f9d47e..331d17fc8 100644
--- a/MediaBrowser.Controller/Entities/Person.cs
+++ b/MediaBrowser.Controller/Entities/Person.cs
@@ -19,6 +19,7 @@ namespace MediaBrowser.Controller.Entities
list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
return list;
}
+
public override string CreatePresentationUniqueKey()
{
return GetUserDataKeys()[0];
@@ -46,7 +47,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Returns the folder containing the item.
- /// If the item is a folder, it returns the folder itself
+ /// If the item is a folder, it returns the folder itself.
/// </summary>
/// <value>The containing folder path.</value>
[JsonIgnore]
@@ -114,11 +115,12 @@ namespace MediaBrowser.Controller.Entities
Logger.LogDebug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
return true;
}
+
return base.RequiresRefresh();
}
/// <summary>
- /// This is called before any metadata refresh and returns true or false indicating if changes were made
+ /// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary>
public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
{
diff --git a/MediaBrowser.Controller/Entities/Photo.cs b/MediaBrowser.Controller/Entities/Photo.cs
index 5ebc9f16a..82d0826c5 100644
--- a/MediaBrowser.Controller/Entities/Photo.cs
+++ b/MediaBrowser.Controller/Entities/Photo.cs
@@ -29,6 +29,7 @@ namespace MediaBrowser.Controller.Entities
return photoAlbum;
}
}
+
return null;
}
}
@@ -68,17 +69,27 @@ namespace MediaBrowser.Controller.Entities
}
public string CameraMake { get; set; }
+
public string CameraModel { get; set; }
+
public string Software { get; set; }
+
public double? ExposureTime { get; set; }
+
public double? FocalLength { get; set; }
+
public ImageOrientation? Orientation { get; set; }
+
public double? Aperture { get; set; }
+
public double? ShutterSpeed { get; set; }
public double? Latitude { get; set; }
+
public double? Longitude { get; set; }
+
public double? Altitude { get; set; }
+
public int? IsoSpeedRating { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Entities/Share.cs b/MediaBrowser.Controller/Entities/Share.cs
index c17789ccc..a51f2b452 100644
--- a/MediaBrowser.Controller/Entities/Share.cs
+++ b/MediaBrowser.Controller/Entities/Share.cs
@@ -8,6 +8,7 @@ namespace MediaBrowser.Controller.Entities
public class Share
{
public string UserId { get; set; }
+
public bool CanEdit { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs
index 068032317..1f64de6a4 100644
--- a/MediaBrowser.Controller/Entities/Studio.cs
+++ b/MediaBrowser.Controller/Entities/Studio.cs
@@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities
{
/// <summary>
- /// Class Studio
+ /// Class Studio.
/// </summary>
public class Studio : BaseItem, IItemByName
{
@@ -18,6 +18,7 @@ namespace MediaBrowser.Controller.Entities
list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
return list;
}
+
public override string CreatePresentationUniqueKey()
{
return GetUserDataKeys()[0];
@@ -25,7 +26,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Returns the folder containing the item.
- /// If the item is a folder, it returns the folder itself
+ /// If the item is a folder, it returns the folder itself.
/// </summary>
/// <value>The containing folder path.</value>
[JsonIgnore]
@@ -93,11 +94,12 @@ namespace MediaBrowser.Controller.Entities
Logger.LogDebug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
return true;
}
+
return base.RequiresRefresh();
}
/// <summary>
- /// This is called before any metadata refresh and returns true or false indicating if changes were made
+ /// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary>
public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
{
diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs
index 4ec60e7cd..9a5f9097d 100644
--- a/MediaBrowser.Controller/Entities/TV/Episode.cs
+++ b/MediaBrowser.Controller/Entities/TV/Episode.cs
@@ -12,7 +12,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities.TV
{
/// <summary>
- /// Class Episode
+ /// Class Episode.
/// </summary>
public class Episode : Video, IHasTrailers, IHasLookupInfo<EpisodeInfo>, IHasSeries
{
@@ -34,7 +34,9 @@ namespace MediaBrowser.Controller.Entities.TV
/// </summary>
/// <value>The aired season.</value>
public int? AirsBeforeSeasonNumber { get; set; }
+
public int? AirsAfterSeasonNumber { get; set; }
+
public int? AirsBeforeEpisodeNumber { get; set; }
/// <summary>
@@ -94,6 +96,7 @@ namespace MediaBrowser.Controller.Entities.TV
{
take--;
}
+
list.InsertRange(0, seriesUserDataKeys.Take(take).Select(i => i + ParentIndexNumber.Value.ToString("000") + IndexNumber.Value.ToString("000")));
}
@@ -101,7 +104,7 @@ namespace MediaBrowser.Controller.Entities.TV
}
/// <summary>
- /// This Episode's Series Instance
+ /// This Episode's Series Instance.
/// </summary>
/// <value>The series.</value>
[JsonIgnore]
@@ -114,6 +117,7 @@ namespace MediaBrowser.Controller.Entities.TV
{
seriesId = FindSeriesId();
}
+
return !seriesId.Equals(Guid.Empty) ? (LibraryManager.GetItemById(seriesId) as Series) : null;
}
}
@@ -128,6 +132,7 @@ namespace MediaBrowser.Controller.Entities.TV
{
seasonId = FindSeasonId();
}
+
return !seasonId.Equals(Guid.Empty) ? (LibraryManager.GetItemById(seasonId) as Season) : null;
}
}
@@ -160,6 +165,7 @@ namespace MediaBrowser.Controller.Entities.TV
{
return "Season " + ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture);
}
+
return "Season Unknown";
}
diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs
index 7dfd1a759..2aba1d03d 100644
--- a/MediaBrowser.Controller/Entities/TV/Season.cs
+++ b/MediaBrowser.Controller/Entities/TV/Season.cs
@@ -11,7 +11,7 @@ using MediaBrowser.Model.Querying;
namespace MediaBrowser.Controller.Entities.TV
{
/// <summary>
- /// Class Season
+ /// Class Season.
/// </summary>
public class Season : Folder, IHasSeries, IHasLookupInfo<SeasonInfo>
{
@@ -68,7 +68,7 @@ namespace MediaBrowser.Controller.Entities.TV
}
/// <summary>
- /// This Episode's Series Instance
+ /// This Episode's Series Instance.
/// </summary>
/// <value>The series.</value>
[JsonIgnore]
@@ -81,6 +81,7 @@ namespace MediaBrowser.Controller.Entities.TV
{
seriesId = FindSeriesId();
}
+
return seriesId == Guid.Empty ? null : (LibraryManager.GetItemById(seriesId) as Series);
}
}
@@ -225,7 +226,7 @@ namespace MediaBrowser.Controller.Entities.TV
}
/// <summary>
- /// This is called before any metadata refresh and returns true or false indicating if changes were made
+ /// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs
index a519089b3..45daa8a53 100644
--- a/MediaBrowser.Controller/Entities/TV/Series.cs
+++ b/MediaBrowser.Controller/Entities/TV/Series.cs
@@ -17,7 +17,7 @@ using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider;
namespace MediaBrowser.Controller.Entities.TV
{
/// <summary>
- /// Class Series
+ /// Class Series.
/// </summary>
public class Series : Folder, IHasTrailers, IHasDisplayOrder, IHasLookupInfo<SeriesInfo>, IMetadataContainer
{
@@ -30,6 +30,7 @@ namespace MediaBrowser.Controller.Entities.TV
}
public DayOfWeek[] AirDays { get; set; }
+
public string AirTime { get; set; }
[JsonIgnore]
@@ -54,7 +55,7 @@ namespace MediaBrowser.Controller.Entities.TV
public IReadOnlyList<Guid> RemoteTrailerIds { get; set; }
/// <summary>
- /// airdate, dvd or absolute
+ /// airdate, dvd or absolute.
/// </summary>
public string DisplayOrder { get; set; }
@@ -150,6 +151,7 @@ namespace MediaBrowser.Controller.Entities.TV
{
query.IncludeItemTypes = new[] { typeof(Episode).Name };
}
+
query.IsVirtualItem = false;
query.Limit = 0;
var totalRecordCount = LibraryManager.GetCount(query);
diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs
index c327d17c9..6b544afc6 100644
--- a/MediaBrowser.Controller/Entities/Trailer.cs
+++ b/MediaBrowser.Controller/Entities/Trailer.cs
@@ -9,7 +9,7 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Controller.Entities
{
/// <summary>
- /// Class Trailer
+ /// Class Trailer.
/// </summary>
public class Trailer : Video, IHasLookupInfo<TrailerInfo>
{
diff --git a/MediaBrowser.Controller/Entities/UserItemData.cs b/MediaBrowser.Controller/Entities/UserItemData.cs
index ab425ee0f..3298fa2d3 100644
--- a/MediaBrowser.Controller/Entities/UserItemData.cs
+++ b/MediaBrowser.Controller/Entities/UserItemData.cs
@@ -4,7 +4,7 @@ using System.Text.Json.Serialization;
namespace MediaBrowser.Controller.Entities
{
/// <summary>
- /// Class UserItemData
+ /// Class UserItemData.
/// </summary>
public class UserItemData
{
@@ -21,11 +21,11 @@ namespace MediaBrowser.Controller.Entities
public string Key { get; set; }
/// <summary>
- /// The _rating
+ /// The _rating.
/// </summary>
private double? _rating;
/// <summary>
- /// Gets or sets the users 0-10 rating
+ /// Gets or sets the users 0-10 rating.
/// </summary>
/// <value>The rating.</value>
/// <exception cref="ArgumentOutOfRangeException">Rating;A 0 to 10 rating is required for UserItemData.</exception>
@@ -105,6 +105,7 @@ namespace MediaBrowser.Controller.Entities
return null;
}
+
set
{
if (value.HasValue)
diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs
index ceca77bc0..1fba8a30f 100644
--- a/MediaBrowser.Controller/Entities/UserView.cs
+++ b/MediaBrowser.Controller/Entities/UserView.cs
@@ -68,7 +68,7 @@ namespace MediaBrowser.Controller.Entities
parent = LibraryManager.GetItemById(ParentId) as Folder ?? parent;
}
- return new UserViewBuilder(UserViewManager, LibraryManager, LoggerFactory.CreateLogger<UserViewBuilder>(), UserDataManager, TVSeriesManager, ConfigurationManager)
+ return new UserViewBuilder(UserViewManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, ConfigurationManager)
.GetUserItems(parent, this, CollectionType, query);
}
diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
index bf0e56cf7..cb35d6e32 100644
--- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs
+++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
@@ -7,6 +7,7 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.TV;
+using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
@@ -22,7 +23,7 @@ namespace MediaBrowser.Controller.Entities
{
private readonly IUserViewManager _userViewManager;
private readonly ILibraryManager _libraryManager;
- private readonly ILogger<UserViewBuilder> _logger;
+ private readonly ILogger<BaseItem> _logger;
private readonly IUserDataManager _userDataManager;
private readonly ITVSeriesManager _tvSeriesManager;
private readonly IServerConfigurationManager _config;
@@ -30,7 +31,7 @@ namespace MediaBrowser.Controller.Entities
public UserViewBuilder(
IUserViewManager userViewManager,
ILibraryManager libraryManager,
- ILogger<UserViewBuilder> logger,
+ ILogger<BaseItem> logger,
IUserDataManager userDataManager,
ITVSeriesManager tvSeriesManager,
IServerConfigurationManager config)
@@ -47,7 +48,7 @@ namespace MediaBrowser.Controller.Entities
{
var user = query.User;
- //if (query.IncludeItemTypes != null &&
+ // if (query.IncludeItemTypes != null &&
// query.IncludeItemTypes.Length == 1 &&
// string.Equals(query.IncludeItemTypes[0], "Playlist", StringComparison.OrdinalIgnoreCase))
//{
@@ -270,7 +271,6 @@ namespace MediaBrowser.Controller.Entities
_logger.LogError(ex, "Error getting genre");
return null;
}
-
})
.Where(i => i != null)
.Select(i => GetUserViewWithName(i.Name, SpecialFolder.MovieGenre, i.SortName, parent));
@@ -347,7 +347,6 @@ namespace MediaBrowser.Controller.Entities
Limit = query.Limit,
StartIndex = query.StartIndex,
UserId = query.User.Id
-
}, parentFolders, query.DtoOptions);
return result;
@@ -384,7 +383,6 @@ namespace MediaBrowser.Controller.Entities
IncludeItemTypes = new[] { typeof(Series).Name },
Recursive = true,
EnableTotalRecordCount = false
-
}).Items
.SelectMany(i => i.Genres)
.DistinctNames()
@@ -399,7 +397,6 @@ namespace MediaBrowser.Controller.Entities
_logger.LogError(ex, "Error getting genre");
return null;
}
-
})
.Where(i => i != null)
.Select(i => GetUserViewWithName(i.Name, SpecialFolder.TvGenre, i.SortName, parent));
@@ -424,7 +421,7 @@ namespace MediaBrowser.Controller.Entities
{
return new QueryResult<BaseItem>
{
- Items = result.Items, //TODO Fix The co-variant conversion between T[] and BaseItem[], this can generate runtime issues if T is not BaseItem.
+ Items = result.Items, // TODO Fix The co-variant conversion between T[] and BaseItem[], this can generate runtime issues if T is not BaseItem.
TotalRecordCount = result.TotalRecordCount
};
}
@@ -964,6 +961,7 @@ namespace MediaBrowser.Controller.Entities
.OfType<Folder>()
.Where(UserView.IsEligibleForGrouping);
}
+
return _libraryManager.GetUserRootFolder()
.GetChildren(user, true)
.OfType<Folder>()
@@ -982,6 +980,7 @@ namespace MediaBrowser.Controller.Entities
return folder != null && viewTypes.Contains(folder.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
}).ToArray();
}
+
return GetMediaFolders(user)
.Where(i =>
{
diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs
index 72eb67a06..b7d7e8e1a 100644
--- a/MediaBrowser.Controller/Entities/Video.cs
+++ b/MediaBrowser.Controller/Entities/Video.cs
@@ -17,7 +17,7 @@ using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.Controller.Entities
{
/// <summary>
- /// Class Video
+ /// Class Video.
/// </summary>
public class Video : BaseItem,
IHasAspectRatio,
@@ -28,7 +28,9 @@ namespace MediaBrowser.Controller.Entities
public string PrimaryVersionId { get; set; }
public string[] AdditionalParts { get; set; }
+
public string[] LocalAlternateVersions { get; set; }
+
public LinkedChild[] LinkedAlternateVersions { get; set; }
[JsonIgnore]
@@ -52,15 +54,18 @@ namespace MediaBrowser.Controller.Entities
{
return false;
}
+
if (extraType.Value == Model.Entities.ExtraType.ThemeVideo)
{
return false;
}
+
if (extraType.Value == Model.Entities.ExtraType.Trailer)
{
return false;
}
}
+
return true;
}
}
@@ -196,6 +201,7 @@ namespace MediaBrowser.Controller.Entities
return video.MediaSourceCount;
}
}
+
return LinkedAlternateVersions.Length + LocalAlternateVersions.Length + 1;
}
}
@@ -390,11 +396,13 @@ namespace MediaBrowser.Controller.Entities
AdditionalParts = newVideo.AdditionalParts;
updateType |= ItemUpdateType.MetadataImport;
}
+
if (!LocalAlternateVersions.SequenceEqual(newVideo.LocalAlternateVersions, StringComparer.Ordinal))
{
LocalAlternateVersions = newVideo.LocalAlternateVersions;
updateType |= ItemUpdateType.MetadataImport;
}
+
if (VideoType != newVideo.VideoType)
{
VideoType = newVideo.VideoType;
@@ -416,6 +424,7 @@ namespace MediaBrowser.Controller.Entities
.Select(i => i.FullName)
.ToArray();
}
+
if (videoType == VideoType.BluRay)
{
return FileSystem.GetFiles(rootPath, new[] { ".m2ts" }, false, true)
@@ -425,6 +434,7 @@ namespace MediaBrowser.Controller.Entities
.Select(i => i.FullName)
.ToArray();
}
+
return Array.Empty<string>();
}
@@ -535,7 +545,6 @@ namespace MediaBrowser.Controller.Entities
{
ItemId = Id,
Index = DefaultVideoStreamIndex.Value
-
}).FirstOrDefault();
}
diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs
index a01ef5c31..c88498640 100644
--- a/MediaBrowser.Controller/Entities/Year.cs
+++ b/MediaBrowser.Controller/Entities/Year.cs
@@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities
{
/// <summary>
- /// Class Year
+ /// Class Year.
/// </summary>
public class Year : BaseItem, IItemByName
{
@@ -21,7 +21,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Returns the folder containing the item.
- /// If the item is a folder, it returns the folder itself
+ /// If the item is a folder, it returns the folder itself.
/// </summary>
/// <value>The containing folder path.</value>
[JsonIgnore]
@@ -103,11 +103,12 @@ namespace MediaBrowser.Controller.Entities
Logger.LogDebug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
return true;
}
+
return base.RequiresRefresh();
}
/// <summary>
- /// This is called before any metadata refresh and returns true or false indicating if changes were made
+ /// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary>
public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
{
diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs
index c0043c0ef..c2932cc4c 100644
--- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs
+++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs
@@ -24,6 +24,11 @@ namespace MediaBrowser.Controller.Extensions
public const string FfmpegAnalyzeDurationKey = "FFmpeg:analyzeduration";
/// <summary>
+ /// The key for the FFmpeg path option.
+ /// </summary>
+ public const string FfmpegPathKey = "ffmpeg";
+
+ /// <summary>
/// The key for a setting that indicates whether playlists should allow duplicate entries.
/// </summary>
public const string PlaylistsAllowDuplicatesKey = "playlists:allowDuplicates";
diff --git a/MediaBrowser.Controller/Extensions/StringExtensions.cs b/MediaBrowser.Controller/Extensions/StringExtensions.cs
index b1aaf6534..e09543e14 100644
--- a/MediaBrowser.Controller/Extensions/StringExtensions.cs
+++ b/MediaBrowser.Controller/Extensions/StringExtensions.cs
@@ -7,7 +7,7 @@ using System.Text.RegularExpressions;
namespace MediaBrowser.Controller.Extensions
{
/// <summary>
- /// Class BaseExtensions
+ /// Class BaseExtensions.
/// </summary>
public static class StringExtensions
{
diff --git a/MediaBrowser.Controller/IO/FileData.cs b/MediaBrowser.Controller/IO/FileData.cs
index 666a3f76b..e655f50eb 100644
--- a/MediaBrowser.Controller/IO/FileData.cs
+++ b/MediaBrowser.Controller/IO/FileData.cs
@@ -20,6 +20,7 @@ namespace MediaBrowser.Controller.IO
{
dict[file.FullName] = file;
}
+
return dict;
}
@@ -49,6 +50,7 @@ namespace MediaBrowser.Controller.IO
{
throw new ArgumentNullException(nameof(path));
}
+
if (args == null)
{
throw new ArgumentNullException(nameof(args));
@@ -77,7 +79,7 @@ namespace MediaBrowser.Controller.IO
if (string.IsNullOrEmpty(newPath))
{
- //invalid shortcut - could be old or target could just be unavailable
+ // invalid shortcut - could be old or target could just be unavailable
logger.LogWarning("Encountered invalid shortcut: " + fullName);
continue;
}
@@ -116,9 +118,9 @@ namespace MediaBrowser.Controller.IO
returnResult[index] = value;
index++;
}
+
return returnResult;
}
-
}
}
diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs
index d1d6c74b8..abdb0f695 100644
--- a/MediaBrowser.Controller/IServerApplicationHost.cs
+++ b/MediaBrowser.Controller/IServerApplicationHost.cs
@@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Http;
namespace MediaBrowser.Controller
{
/// <summary>
- /// Interface IServerApplicationHost
+ /// Interface IServerApplicationHost.
/// </summary>
public interface IServerApplicationHost : IApplicationHost
{
diff --git a/MediaBrowser.Controller/IServerApplicationPaths.cs b/MediaBrowser.Controller/IServerApplicationPaths.cs
index c35a22ac7..155bf9177 100644
--- a/MediaBrowser.Controller/IServerApplicationPaths.cs
+++ b/MediaBrowser.Controller/IServerApplicationPaths.cs
@@ -5,7 +5,7 @@ namespace MediaBrowser.Controller
public interface IServerApplicationPaths : IApplicationPaths
{
/// <summary>
- /// Gets the path to the base root media directory
+ /// Gets the path to the base root media directory.
/// </summary>
/// <value>The root folder path.</value>
string RootFolderPath { get; }
@@ -17,13 +17,13 @@ namespace MediaBrowser.Controller
string DefaultUserViewsPath { get; }
/// <summary>
- /// Gets the path to the People directory
+ /// Gets the path to the People directory.
/// </summary>
/// <value>The people path.</value>
string PeoplePath { get; }
/// <summary>
- /// Gets the path to the Genre directory
+ /// Gets the path to the Genre directory.
/// </summary>
/// <value>The genre path.</value>
string GenrePath { get; }
@@ -35,25 +35,25 @@ namespace MediaBrowser.Controller
string MusicGenrePath { get; }
/// <summary>
- /// Gets the path to the Studio directory
+ /// Gets the path to the Studio directory.
/// </summary>
/// <value>The studio path.</value>
string StudioPath { get; }
/// <summary>
- /// Gets the path to the Year directory
+ /// Gets the path to the Year directory.
/// </summary>
/// <value>The year path.</value>
string YearPath { get; }
/// <summary>
- /// Gets the path to the General IBN directory
+ /// Gets the path to the General IBN directory.
/// </summary>
/// <value>The general path.</value>
string GeneralPath { get; }
/// <summary>
- /// Gets the path to the Ratings IBN directory
+ /// Gets the path to the Ratings IBN directory.
/// </summary>
/// <value>The ratings path.</value>
string RatingsPath { get; }
@@ -65,7 +65,7 @@ namespace MediaBrowser.Controller
string MediaInfoImagesPath { get; }
/// <summary>
- /// Gets the path to the user configuration directory
+ /// Gets the path to the user configuration directory.
/// </summary>
/// <value>The user configuration directory path.</value>
string UserConfigurationDirectoryPath { get; }
diff --git a/MediaBrowser.Controller/Library/DeleteOptions.cs b/MediaBrowser.Controller/Library/DeleteOptions.cs
index 751b90481..2944d8259 100644
--- a/MediaBrowser.Controller/Library/DeleteOptions.cs
+++ b/MediaBrowser.Controller/Library/DeleteOptions.cs
@@ -3,6 +3,7 @@ namespace MediaBrowser.Controller.Library
public class DeleteOptions
{
public bool DeleteFileLocation { get; set; }
+
public bool DeleteFromExternalProvider { get; set; }
public DeleteOptions()
diff --git a/MediaBrowser.Controller/Library/IIntroProvider.cs b/MediaBrowser.Controller/Library/IIntroProvider.cs
index aa7001611..d45493d40 100644
--- a/MediaBrowser.Controller/Library/IIntroProvider.cs
+++ b/MediaBrowser.Controller/Library/IIntroProvider.cs
@@ -5,7 +5,7 @@ using MediaBrowser.Controller.Entities;
namespace MediaBrowser.Controller.Library
{
/// <summary>
- /// Class BaseIntroProvider
+ /// Class BaseIntroProvider.
/// </summary>
public interface IIntroProvider
{
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index d7237039e..9d6646857 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -21,7 +21,7 @@ using Person = MediaBrowser.Controller.Entities.Person;
namespace MediaBrowser.Controller.Library
{
/// <summary>
- /// Interface ILibraryManager
+ /// Interface ILibraryManager.
/// </summary>
public interface ILibraryManager
{
@@ -30,15 +30,13 @@ namespace MediaBrowser.Controller.Library
/// </summary>
/// <param name="fileInfo">The file information.</param>
/// <param name="parent">The parent.</param>
- /// <param name="allowIgnorePath">Allow the path to be ignored.</param>
/// <returns>BaseItem.</returns>
BaseItem ResolvePath(
FileSystemMetadata fileInfo,
- Folder parent = null,
- bool allowIgnorePath = true);
+ Folder parent = null);
/// <summary>
- /// Resolves a set of files into a list of BaseItem
+ /// Resolves a set of files into a list of BaseItem.
/// </summary>
IEnumerable<BaseItem> ResolvePaths(
IEnumerable<FileSystemMetadata> files,
@@ -54,7 +52,7 @@ namespace MediaBrowser.Controller.Library
AggregateFolder RootFolder { get; }
/// <summary>
- /// Gets a Person
+ /// Gets a Person.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>Task{Person}.</returns>
@@ -75,14 +73,14 @@ namespace MediaBrowser.Controller.Library
MusicArtist GetArtist(string name);
MusicArtist GetArtist(string name, DtoOptions options);
/// <summary>
- /// Gets a Studio
+ /// Gets a Studio.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>Task{Studio}.</returns>
Studio GetStudio(string name);
/// <summary>
- /// Gets a Genre
+ /// Gets a Genre.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>Task{Genre}.</returns>
@@ -96,7 +94,7 @@ namespace MediaBrowser.Controller.Library
MusicGenre GetMusicGenre(string name);
/// <summary>
- /// Gets a Year
+ /// Gets a Year.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>Task{Year}.</returns>
@@ -113,7 +111,7 @@ namespace MediaBrowser.Controller.Library
Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress);
/// <summary>
- /// Reloads the root media folder
+ /// Reloads the root media folder.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
diff --git a/MediaBrowser.Controller/Library/ILibraryPostScanTask.cs b/MediaBrowser.Controller/Library/ILibraryPostScanTask.cs
index cba5e8fd7..4032e9d83 100644
--- a/MediaBrowser.Controller/Library/ILibraryPostScanTask.cs
+++ b/MediaBrowser.Controller/Library/ILibraryPostScanTask.cs
@@ -5,7 +5,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Controller.Library
{
/// <summary>
- /// An interface for tasks that run after the media library scan
+ /// An interface for tasks that run after the media library scan.
/// </summary>
public interface ILibraryPostScanTask
{
diff --git a/MediaBrowser.Controller/Library/ILiveStream.cs b/MediaBrowser.Controller/Library/ILiveStream.cs
index 734932f17..7c9a9b20e 100644
--- a/MediaBrowser.Controller/Library/ILiveStream.cs
+++ b/MediaBrowser.Controller/Library/ILiveStream.cs
@@ -9,10 +9,15 @@ namespace MediaBrowser.Controller.Library
Task Open(CancellationToken openCancellationToken);
Task Close();
int ConsumerCount { get; set; }
+
string OriginalStreamId { get; set; }
+
string TunerHostId { get; }
+
bool EnableStreamSharing { get; }
+
MediaSourceInfo MediaSource { get; set; }
+
string UniqueId { get; }
}
}
diff --git a/MediaBrowser.Controller/Library/IMetadataSaver.cs b/MediaBrowser.Controller/Library/IMetadataSaver.cs
index dd119984e..027cc5b40 100644
--- a/MediaBrowser.Controller/Library/IMetadataSaver.cs
+++ b/MediaBrowser.Controller/Library/IMetadataSaver.cs
@@ -4,7 +4,7 @@ using MediaBrowser.Controller.Entities;
namespace MediaBrowser.Controller.Library
{
/// <summary>
- /// Interface IMetadataSaver
+ /// Interface IMetadataSaver.
/// </summary>
public interface IMetadataSaver
{
diff --git a/MediaBrowser.Controller/Library/ISearchEngine.cs b/MediaBrowser.Controller/Library/ISearchEngine.cs
index 8498b92ae..31dcbba5b 100644
--- a/MediaBrowser.Controller/Library/ISearchEngine.cs
+++ b/MediaBrowser.Controller/Library/ISearchEngine.cs
@@ -4,7 +4,7 @@ using MediaBrowser.Model.Search;
namespace MediaBrowser.Controller.Library
{
/// <summary>
- /// Interface ILibrarySearchEngine
+ /// Interface ILibrarySearchEngine.
/// </summary>
public interface ISearchEngine
{
diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs
index f5ccad671..d08ad4cac 100644
--- a/MediaBrowser.Controller/Library/IUserDataManager.cs
+++ b/MediaBrowser.Controller/Library/IUserDataManager.cs
@@ -42,14 +42,14 @@ namespace MediaBrowser.Controller.Library
UserItemDataDto GetUserDataDto(BaseItem item, BaseItemDto itemDto, User user, DtoOptions dto_options);
/// <summary>
- /// Get all user data for the given user
+ /// Get all user data for the given user.
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
List<UserItemData> GetAllUserData(Guid userId);
/// <summary>
- /// Save the all provided user data for the given user
+ /// Save the all provided user data for the given user.
/// </summary>
/// <param name="userId"></param>
/// <param name="userData"></param>
@@ -58,7 +58,7 @@ namespace MediaBrowser.Controller.Library
void SaveAllUserData(Guid userId, UserItemData[] userData, CancellationToken cancellationToken);
/// <summary>
- /// Updates playstate for an item and returns true or false indicating if it was played to completion
+ /// Updates playstate for an item and returns true or false indicating if it was played to completion.
/// </summary>
bool UpdatePlayState(BaseItem item, UserItemData data, long? positionTicks);
}
diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs
index b5b2e4729..e73fe7120 100644
--- a/MediaBrowser.Controller/Library/IUserManager.cs
+++ b/MediaBrowser.Controller/Library/IUserManager.cs
@@ -11,7 +11,7 @@ using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Library
{
/// <summary>
- /// Interface IUserManager
+ /// Interface IUserManager.
/// </summary>
public interface IUserManager
{
@@ -111,8 +111,8 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Deletes the specified user.
/// </summary>
- /// <param name="user">The user to be deleted.</param>
- void DeleteUser(User user);
+ /// <param name="userId">The id of the user to be deleted.</param>
+ void DeleteUser(Guid userId);
/// <summary>
/// Resets the password.
diff --git a/MediaBrowser.Controller/Library/ItemChangeEventArgs.cs b/MediaBrowser.Controller/Library/ItemChangeEventArgs.cs
index c9671de47..b5c48321b 100644
--- a/MediaBrowser.Controller/Library/ItemChangeEventArgs.cs
+++ b/MediaBrowser.Controller/Library/ItemChangeEventArgs.cs
@@ -3,7 +3,7 @@ using MediaBrowser.Controller.Entities;
namespace MediaBrowser.Controller.Library
{
/// <summary>
- /// Class ItemChangeEventArgs
+ /// Class ItemChangeEventArgs.
/// </summary>
public class ItemChangeEventArgs
{
diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs
index 0222b926e..2e5dcc4c5 100644
--- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs
+++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs
@@ -15,7 +15,7 @@ namespace MediaBrowser.Controller.Library
public class ItemResolveArgs : EventArgs
{
/// <summary>
- /// The _app paths
+ /// The _app paths.
/// </summary>
private readonly IServerApplicationPaths _appPaths;
@@ -42,7 +42,7 @@ namespace MediaBrowser.Controller.Library
public LibraryOptions GetLibraryOptions()
{
- return LibraryOptions ?? (LibraryOptions = (Parent == null ? new LibraryOptions() : BaseItem.LibraryManager.GetLibraryOptions(Parent)));
+ return LibraryOptions ?? (LibraryOptions = Parent == null ? new LibraryOptions() : BaseItem.LibraryManager.GetLibraryOptions(Parent));
}
/// <summary>
@@ -89,7 +89,6 @@ namespace MediaBrowser.Controller.Library
return parentDir.Length > _appPaths.RootFolderPath.Length
&& parentDir.StartsWith(_appPaths.RootFolderPath, StringComparison.OrdinalIgnoreCase);
-
}
}
@@ -129,8 +128,8 @@ namespace MediaBrowser.Controller.Library
}
return item != null;
-
}
+
return false;
}
@@ -225,8 +224,6 @@ namespace MediaBrowser.Controller.Library
public string CollectionType { get; set; }
- #region Equality Overrides
-
/// <summary>
/// Determines whether the specified <see cref="object" /> is equal to this instance.
/// </summary>
@@ -255,13 +252,15 @@ namespace MediaBrowser.Controller.Library
{
if (args != null)
{
- if (args.Path == null && Path == null) return true;
+ if (args.Path == null && Path == null)
+ {
+ return true;
+ }
+
return args.Path != null && BaseItem.FileSystem.AreEqual(args.Path, Path);
}
+
return false;
}
-
- #endregion
}
-
}
diff --git a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs
index b4e205184..08cfea3c3 100644
--- a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs
+++ b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs
@@ -8,23 +8,32 @@ using MediaBrowser.Model.Dto;
namespace MediaBrowser.Controller.Library
{
/// <summary>
- /// Holds information about a playback progress event
+ /// Holds information about a playback progress event.
/// </summary>
public class PlaybackProgressEventArgs : EventArgs
{
public List<User> Users { get; set; }
+
public long? PlaybackPositionTicks { get; set; }
+
public BaseItem Item { get; set; }
+
public BaseItemDto MediaInfo { get; set; }
+
public string MediaSourceId { get; set; }
+
public bool IsPaused { get; set; }
+
public bool IsAutomated { get; set; }
public string DeviceId { get; set; }
+
public string DeviceName { get; set; }
+
public string ClientName { get; set; }
public string PlaySessionId { get; set; }
+
public SessionInfo Session { get; set; }
public PlaybackProgressEventArgs()
diff --git a/MediaBrowser.Controller/Library/Profiler.cs b/MediaBrowser.Controller/Library/Profiler.cs
index 0febef3d3..399378a09 100644
--- a/MediaBrowser.Controller/Library/Profiler.cs
+++ b/MediaBrowser.Controller/Library/Profiler.cs
@@ -5,21 +5,21 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Library
{
/// <summary>
- /// Class Profiler
+ /// Class Profiler.
/// </summary>
public class Profiler : IDisposable
{
/// <summary>
- /// The name
+ /// The name.
/// </summary>
readonly string _name;
/// <summary>
- /// The stopwatch
+ /// The stopwatch.
/// </summary>
readonly Stopwatch _stopwatch;
/// <summary>
- /// The _logger
+ /// The _logger.
/// </summary>
private readonly ILogger<Profiler> _logger;
@@ -37,7 +37,6 @@ namespace MediaBrowser.Controller.Library
_stopwatch = new Stopwatch();
_stopwatch.Start();
}
- #region IDisposable Members
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
@@ -67,10 +66,9 @@ namespace MediaBrowser.Controller.Library
message = string.Format("{0} took {1} seconds.",
_name, ((float)_stopwatch.ElapsedMilliseconds / 1000).ToString("#0.000"));
}
+
_logger.LogInformation(message);
}
}
-
- #endregion
}
}
diff --git a/MediaBrowser.Controller/Library/SearchHintInfo.cs b/MediaBrowser.Controller/Library/SearchHintInfo.cs
index 692431e34..897c2b7f4 100644
--- a/MediaBrowser.Controller/Library/SearchHintInfo.cs
+++ b/MediaBrowser.Controller/Library/SearchHintInfo.cs
@@ -3,7 +3,7 @@ using MediaBrowser.Controller.Entities;
namespace MediaBrowser.Controller.Library
{
/// <summary>
- /// Class SearchHintInfo
+ /// Class SearchHintInfo.
/// </summary>
public class SearchHintInfo
{
diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs
index fd5fb6748..a3aa6019e 100644
--- a/MediaBrowser.Controller/Library/TVUtils.cs
+++ b/MediaBrowser.Controller/Library/TVUtils.cs
@@ -3,7 +3,7 @@ using System;
namespace MediaBrowser.Controller.Library
{
/// <summary>
- /// Class TVUtils
+ /// Class TVUtils.
/// </summary>
public static class TVUtils
{
@@ -38,8 +38,9 @@ namespace MediaBrowser.Controller.Library
};
}
- return new DayOfWeek[] { };
+ return Array.Empty<DayOfWeek>();
}
+
return null;
}
}
diff --git a/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs b/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs
index 3e7351b8b..fa0192784 100644
--- a/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs
+++ b/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs
@@ -6,7 +6,7 @@ using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Library
{
/// <summary>
- /// Class UserDataSaveEventArgs
+ /// Class UserDataSaveEventArgs.
/// </summary>
public class UserDataSaveEventArgs : EventArgs
{
diff --git a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
index 70477fce7..67d0df4fd 100644
--- a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
@@ -3,7 +3,7 @@ using MediaBrowser.Model.LiveTv;
namespace MediaBrowser.Controller.LiveTv
{
/// <summary>
- /// Class ChannelInfo
+ /// Class ChannelInfo.
/// </summary>
public class ChannelInfo
{
@@ -44,13 +44,13 @@ namespace MediaBrowser.Controller.LiveTv
public ChannelType ChannelType { get; set; }
/// <summary>
- /// Supply the image path if it can be accessed directly from the file system
+ /// Supply the image path if it can be accessed directly from the file system.
/// </summary>
/// <value>The image path.</value>
public string ImagePath { get; set; }
/// <summary>
- /// Supply the image url if it can be downloaded
+ /// Supply the image url if it can be downloaded.
/// </summary>
/// <value>The image URL.</value>
public string ImageUrl { get; set; }
@@ -67,8 +67,11 @@ namespace MediaBrowser.Controller.LiveTv
public bool? IsFavorite { get; set; }
public bool? IsHD { get; set; }
+
public string AudioCodec { get; set; }
+
public string VideoCodec { get; set; }
+
public string[] Tags { get; set; }
}
}
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
index bc3bf78f0..f619b011b 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
@@ -14,7 +14,7 @@ using MediaBrowser.Model.Querying;
namespace MediaBrowser.Controller.LiveTv
{
/// <summary>
- /// Manages all live tv services installed on the server
+ /// Manages all live tv services installed on the server.
/// </summary>
public interface ILiveTvManager
{
@@ -286,8 +286,11 @@ namespace MediaBrowser.Controller.LiveTv
public class ActiveRecordingInfo
{
public string Id { get; set; }
+
public string Path { get; set; }
+
public TimerInfo Timer { get; set; }
+
public CancellationTokenSource CancellationTokenSource { get; set; }
}
}
diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs
index 240ba8c23..3679e4f78 100644
--- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs
+++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs
@@ -50,6 +50,7 @@ namespace MediaBrowser.Controller.LiveTv
get;
}
}
+
public interface IConfigurableTunerHost
{
/// <summary>
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvConflictException.cs b/MediaBrowser.Controller/LiveTv/LiveTvConflictException.cs
index df8f91eec..0e09d1aeb 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvConflictException.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvConflictException.cs
@@ -9,12 +9,11 @@ namespace MediaBrowser.Controller.LiveTv
{
public LiveTvConflictException()
{
-
}
+
public LiveTvConflictException(string message)
: base(message)
{
-
}
}
}
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
index 5b901d223..472b061e6 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
@@ -140,14 +140,14 @@ namespace MediaBrowser.Controller.LiveTv
/// <summary>
/// Returns the folder containing the item.
- /// If the item is a folder, it returns the folder itself
+ /// If the item is a folder, it returns the folder itself.
/// </summary>
/// <value>The containing folder path.</value>
[JsonIgnore]
public override string ContainingFolderPath => Path;
//[JsonIgnore]
- //public override string MediaType
+ // public override string MediaType
//{
// get
// {
diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
index 5d0f13192..d06a15323 100644
--- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
@@ -18,7 +18,7 @@ namespace MediaBrowser.Controller.LiveTv
public string ChannelId { get; set; }
/// <summary>
- /// Name of the program
+ /// Name of the program.
/// </summary>
public string Name { get; set; }
@@ -95,13 +95,13 @@ namespace MediaBrowser.Controller.LiveTv
public string EpisodeTitle { get; set; }
/// <summary>
- /// Supply the image path if it can be accessed directly from the file system
+ /// Supply the image path if it can be accessed directly from the file system.
/// </summary>
/// <value>The image path.</value>
public string ImagePath { get; set; }
/// <summary>
- /// Supply the image url if it can be downloaded
+ /// Supply the image url if it can be downloaded.
/// </summary>
/// <value>The image URL.</value>
public string ImageUrl { get; set; }
@@ -199,6 +199,7 @@ namespace MediaBrowser.Controller.LiveTv
public string Etag { get; set; }
public Dictionary<string, string> ProviderIds { get; set; }
+
public Dictionary<string, string> SeriesProviderIds { get; set; }
public ProgramInfo()
diff --git a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs
index 432388d6b..b9e0218ab 100644
--- a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs
@@ -169,13 +169,13 @@ namespace MediaBrowser.Controller.LiveTv
public float? CommunityRating { get; set; }
/// <summary>
- /// Supply the image path if it can be accessed directly from the file system
+ /// Supply the image path if it can be accessed directly from the file system.
/// </summary>
/// <value>The image path.</value>
public string ImagePath { get; set; }
/// <summary>
- /// Supply the image url if it can be downloaded
+ /// Supply the image url if it can be downloaded.
/// </summary>
/// <value>The image URL.</value>
public string ImageUrl { get; set; }
diff --git a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs
index 4fbd496c5..6e7acaae3 100644
--- a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs
@@ -57,6 +57,7 @@ namespace MediaBrowser.Controller.LiveTv
public bool RecordAnyChannel { get; set; }
public int KeepUpTo { get; set; }
+
public KeepUntil KeepUntil { get; set; }
public bool SkipEpisodesInLibrary { get; set; }
diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs
index 46774b2b7..df98bb6af 100644
--- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs
@@ -18,7 +18,9 @@ namespace MediaBrowser.Controller.LiveTv
}
public Dictionary<string, string> ProviderIds { get; set; }
+
public Dictionary<string, string> SeriesProviderIds { get; set; }
+
public string[] Tags { get; set; }
/// <summary>
@@ -146,10 +148,15 @@ namespace MediaBrowser.Controller.LiveTv
public bool IsRepeat { get; set; }
public string HomePageUrl { get; set; }
+
public float? CommunityRating { get; set; }
+
public string OfficialRating { get; set; }
+
public string[] Genres { get; set; }
+
public string RecordingPath { get; set; }
+
public KeepUntil KeepUntil { get; set; }
}
}
diff --git a/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs b/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs
index cb02da635..df3f55c26 100644
--- a/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs
+++ b/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs
@@ -3,8 +3,11 @@ namespace MediaBrowser.Controller.LiveTv
public class TunerChannelMapping
{
public string Name { get; set; }
+
public string ProviderChannelName { get; set; }
+
public string ProviderChannelId { get; set; }
+
public string Id { get; set; }
}
}
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index 73e966344..67f17f7a5 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -13,8 +13,8 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.5" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.5" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.6" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.6" />
</ItemGroup>
<ItemGroup>
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 8ce106469..ffbda42c3 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Jellyfin.Data.Enums;
@@ -74,7 +75,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{"omx", hwEncoder + "_omx"},
{hwEncoder + "_v4l2m2m", hwEncoder + "_v4l2m2m"},
{"mediacodec", hwEncoder + "_mediacodec"},
- {"vaapi", hwEncoder + "_vaapi"}
+ {"vaapi", hwEncoder + "_vaapi"},
+ {"videotoolbox", hwEncoder + "_videotoolbox"}
};
if (!string.IsNullOrEmpty(hwType)
@@ -104,11 +106,12 @@ namespace MediaBrowser.Controller.MediaEncoding
return false;
}
- return true;
+ return _mediaEncoder.SupportsHwaccel("vaapi");
+
}
/// <summary>
- /// Gets the name of the output video codec
+ /// Gets the name of the output video codec.
/// </summary>
public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
{
@@ -285,7 +288,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Infers the audio codec based on the url
+ /// Infers the audio codec based on the url.
/// </summary>
public string InferAudioCodec(string container)
{
@@ -445,33 +448,61 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetInputArgument(EncodingJobInfo state, EncodingOptions encodingOptions)
{
var arg = new StringBuilder();
+ var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty;
+ var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty;
+ var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
+ var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
+ var isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
+ var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
+ var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
+
+ if (!IsCopyCodec(outputVideoCodec))
+ {
+ if (state.IsVideoRequest
+ && IsVaapiSupported(state)
+ && string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ if (isVaapiDecoder)
+ {
+ arg.Append("-hwaccel_output_format vaapi ")
+ .Append("-vaapi_device ")
+ .Append(encodingOptions.VaapiDevice)
+ .Append(" ");
+ }
+ else if (!isVaapiDecoder && isVaapiEncoder)
+ {
+ arg.Append("-vaapi_device ")
+ .Append(encodingOptions.VaapiDevice)
+ .Append(" ");
+ }
+ }
- if (state.IsVideoRequest
- && string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
- {
- arg.Append("-hwaccel vaapi -hwaccel_output_format vaapi")
- .Append(" -vaapi_device ")
- .Append(encodingOptions.VaapiDevice)
- .Append(' ');
- }
-
- if (state.IsVideoRequest
- && string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
- {
- var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions);
- var outputVideoCodec = GetVideoEncoder(state, encodingOptions);
-
- var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
-
- if (!hasTextSubs)
+ if (state.IsVideoRequest
+ && string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
{
- // While using QSV encoder
- if ((outputVideoCodec ?? string.Empty).IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1)
+ var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+
+ if (isQsvEncoder)
{
- // While using QSV decoder
- if ((videoDecoder ?? string.Empty).IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1)
+ if (isQsvDecoder)
{
- arg.Append("-hwaccel qsv ");
+ if (isLinux)
+ {
+ if (hasGraphicalSubs)
+ {
+ arg.Append("-init_hw_device qsv=hw -filter_hw_device hw ");
+ }
+ else
+ {
+ arg.Append("-hwaccel qsv -init_hw_device qsv=hw ");
+ }
+ }
+
+ if (isWindows)
+ {
+ arg.Append("-hwaccel qsv -init_hw_device qsv=hw ");
+ }
}
// While using SW decoder
else
@@ -527,6 +558,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|| codec.IndexOf("hevc", StringComparison.OrdinalIgnoreCase) != -1;
}
+ // TODO This is auto inserted into the mpegts mux so it might not be needed
+ // https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb
public string GetBitStreamArgs(MediaStream stream)
{
if (IsH264(stream))
@@ -551,8 +584,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
{
- // With vpx when crf is used, b:v becomes a max rate
- // https://trac.ffmpeg.org/wiki/vpxEncodingGuide.
+ // When crf is used with vpx, b:v becomes a max rate
+ // https://trac.ffmpeg.org/wiki/Encode/VP9
return string.Format(
CultureInfo.InvariantCulture,
" -maxrate:v {0} -bufsize:v {1} -b:v {0}",
@@ -703,7 +736,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Gets the video bitrate to specify on the command line
+ /// Gets the video bitrate to specify on the command line.
/// </summary>
public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, string defaultPreset)
{
@@ -759,7 +792,6 @@ namespace MediaBrowser.Controller.MediaEncoding
}
param += " -look_ahead 0";
-
}
else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
|| string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase))
@@ -768,7 +800,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
case "veryslow":
- param += "-preset slow"; //lossless is only supported on maxwell and newer(2014+)
+ param += "-preset slow"; // lossless is only supported on maxwell and newer(2014+)
break;
case "slow":
@@ -793,6 +825,34 @@ namespace MediaBrowser.Controller.MediaEncoding
break;
}
}
+ else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
+ {
+ switch (encodingOptions.EncoderPreset)
+ {
+ case "veryslow":
+ case "slow":
+ case "slower":
+ param += "-quality quality";
+ break;
+
+ case "medium":
+ param += "-quality balanced";
+ break;
+
+ case "fast":
+ case "faster":
+ case "veryfast":
+ case "superfast":
+ case "ultrafast":
+ param += "-quality speed";
+ break;
+
+ default:
+ param += "-quality speed";
+ break;
+ }
+ }
else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // webm
{
// Values 0-3, 0 being highest quality but slower
@@ -999,7 +1059,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (string.IsNullOrEmpty(videoStream.Profile))
{
- //return false;
+ // return false;
}
var requestedProfile = requestedProfiles[0];
@@ -1072,7 +1132,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (!videoStream.Level.HasValue)
{
- //return false;
+ // return false;
}
if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel)
@@ -1301,7 +1361,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Gets the number of audio channels to specify on the command line
+ /// Gets the number of audio channels to specify on the command line.
/// </summary>
/// <param name="state">The state.</param>
/// <param name="audioStream">The audio stream.</param>
@@ -1327,7 +1387,6 @@ namespace MediaBrowser.Controller.MediaEncoding
// wmav2 currently only supports two channel output
transcoderChannelLimit = 2;
}
-
else if (codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1)
{
// libmp3lame currently only supports two channel output
@@ -1339,7 +1398,7 @@ namespace MediaBrowser.Controller.MediaEncoding
transcoderChannelLimit = 6;
}
- var isTranscodingAudio = !EncodingHelper.IsCopyCodec(codec);
+ var isTranscodingAudio = !IsCopyCodec(codec);
int? resultChannels = state.GetRequestedAudioChannels(codec);
if (isTranscodingAudio)
@@ -1462,7 +1521,6 @@ namespace MediaBrowser.Controller.MediaEncoding
" -map 0:{0}",
state.AudioStream.Index);
}
-
else
{
args += " -map -0:a";
@@ -1489,7 +1547,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Determines which stream will be used for playback
+ /// Determines which stream will be used for playback.
/// </summary>
/// <param name="allStream">All stream.</param>
/// <param name="desiredIndex">Index of the desired.</param>
@@ -1528,8 +1586,9 @@ namespace MediaBrowser.Controller.MediaEncoding
EncodingOptions options,
string outputVideoCodec)
{
- var outputSizeParam = string.Empty;
+ outputVideoCodec ??= string.Empty;
+ var outputSizeParam = string.Empty;
var request = state.BaseRequest;
// Add resolution params, if specified
@@ -1543,28 +1602,28 @@ namespace MediaBrowser.Controller.MediaEncoding
var index = outputSizeParam.IndexOf("hwdownload", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
- outputSizeParam = "," + outputSizeParam.Substring(index);
+ outputSizeParam = outputSizeParam.Substring(index);
}
else
{
index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
- outputSizeParam = "," + outputSizeParam.Substring(index);
+ outputSizeParam = outputSizeParam.Substring(index);
}
else
{
index = outputSizeParam.IndexOf("yadif", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
- outputSizeParam = "," + outputSizeParam.Substring(index);
+ outputSizeParam = outputSizeParam.Substring(index);
}
else
{
index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
- outputSizeParam = "," + outputSizeParam.Substring(index);
+ outputSizeParam = outputSizeParam.Substring(index);
}
}
}
@@ -1572,44 +1631,31 @@ namespace MediaBrowser.Controller.MediaEncoding
}
var videoSizeParam = string.Empty;
- var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options);
+ var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty;
+ var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
// Setup subtitle scaling
if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue)
{
- // force_original_aspect_ratio=decrease
- // Enable decreasing output video width or height if necessary to keep the original aspect ratio
- videoSizeParam = string.Format(
- CultureInfo.InvariantCulture,
- "scale={0}:{1}:force_original_aspect_ratio=decrease",
- state.VideoStream.Width.Value,
- state.VideoStream.Height.Value);
-
- // For QSV, feed it into hardware encoder now
- if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
- {
- videoSizeParam += ",hwupload=extra_hw_frames=64";
- }
+ // Adjust the size of graphical subtitles to fit the video stream.
+ var videoStream = state.VideoStream;
+ var inputWidth = videoStream?.Width;
+ var inputHeight = videoStream?.Height;
+ var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight);
- // For VAAPI and CUVID decoder
- // these encoders cannot automatically adjust the size of graphical subtitles to fit the output video,
- // thus needs to be manually adjusted.
- if ((IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
- || (videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1)
+ if (width.HasValue && height.HasValue)
{
- var videoStream = state.VideoStream;
- var inputWidth = videoStream?.Width;
- var inputHeight = videoStream?.Height;
- var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight);
-
- if (width.HasValue && height.HasValue)
- {
- videoSizeParam = string.Format(
+ videoSizeParam = string.Format(
CultureInfo.InvariantCulture,
- "scale={0}:{1}:force_original_aspect_ratio=decrease",
+ "scale={0}x{1}",
width.Value,
height.Value);
- }
+ }
+
+ // For QSV, feed it into hardware encoder now
+ if (isLinux && string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+ {
+ videoSizeParam += ",hwupload=extra_hw_frames=64";
}
}
@@ -1622,7 +1668,10 @@ namespace MediaBrowser.Controller.MediaEncoding
: state.SubtitleStream.Index;
// Setup default filtergraph utilizing FFMpeg overlay() and FFMpeg scale() (see the return of this function for index reference)
- var retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay{3}\"";
+ // Always put the scaler before the overlay for better performance
+ var retStr = !string.IsNullOrEmpty(outputSizeParam) ?
+ " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"" :
+ " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\"";
// When the input may or may not be hardware VAAPI decodable
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
@@ -1632,12 +1681,11 @@ namespace MediaBrowser.Controller.MediaEncoding
[sub]: SW scaling subtitle to FixedOutputSize
[base][sub]: SW overlay
*/
- outputSizeParam = outputSizeParam.TrimStart(',');
retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"";
}
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
- else if (IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
+ else if (IsVaapiSupported(state) && videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1
&& string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
{
/*
@@ -1645,24 +1693,21 @@ namespace MediaBrowser.Controller.MediaEncoding
[sub]: SW scaling subtitle to FixedOutputSize
[base][sub]: SW overlay
*/
- outputSizeParam = outputSizeParam.TrimStart(',');
retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"";
}
-
else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
/*
QSV in FFMpeg can now setup hardware overlay for transcodes.
For software decoding and hardware encoding option, frames must be hwuploaded into hardware
with fixed frame size.
+ Currently only supports linux.
*/
- if (!string.IsNullOrEmpty(videoDecoder) && videoDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase))
+ if (isLinux)
{
- retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv=x=(W-w)/2:y=(H-h)/2{3}\"";
- }
- else
- {
- retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]hwupload=extra_hw_frames=64[v];[v][sub]overlay_qsv=x=(W-w)/2:y=(H-h)/2{3}\"";
+ retStr = !string.IsNullOrEmpty(outputSizeParam) ?
+ " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\"" :
+ " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\"";
}
}
@@ -1688,6 +1733,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return (null, null);
}
+
if (!videoHeight.HasValue && !requestedHeight.HasValue)
{
return (null, null);
@@ -1733,10 +1779,8 @@ namespace MediaBrowser.Controller.MediaEncoding
requestedMaxWidth,
requestedMaxHeight);
- var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
-
if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
- || (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && !hasTextSubs))
+ || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
&& width.HasValue
&& height.HasValue)
{
@@ -1745,7 +1789,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// output dimensions. Output dimensions are guaranteed to be even.
var outputWidth = width.Value;
var outputHeight = height.Value;
- var vaapi_or_qsv = string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) ? "qsv" : "vaapi";
+ var qsv_or_vaapi = string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase);
if (!videoWidth.HasValue
|| outputWidth != videoWidth.Value
@@ -1753,17 +1797,24 @@ namespace MediaBrowser.Controller.MediaEncoding
|| outputHeight != videoHeight.Value)
{
// Force nv12 pixel format to enable 10-bit to 8-bit colour conversion.
+ // use vpp_qsv filter to avoid green bar when the fixed output size is requested.
filters.Add(
string.Format(
CultureInfo.InvariantCulture,
- "scale_{0}=w={1}:h={2}:format=nv12",
- vaapi_or_qsv,
+ "{0}=w={1}:h={2}:format=nv12{3}",
+ qsv_or_vaapi ? "vpp_qsv" : "scale_vaapi",
outputWidth,
- outputHeight));
+ outputHeight,
+ (qsv_or_vaapi && state.DeInterlace("h264", true)) ? ":deinterlace=1" : string.Empty));
}
else
{
- filters.Add(string.Format(CultureInfo.InvariantCulture, "scale_{0}=format=nv12", vaapi_or_qsv));
+ filters.Add(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}=format=nv12{1}",
+ qsv_or_vaapi ? "vpp_qsv" : "scale_vaapi",
+ (qsv_or_vaapi && state.DeInterlace("h264", true)) ? ":deinterlace=1" : string.Empty));
}
}
else if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1
@@ -1933,11 +1984,11 @@ namespace MediaBrowser.Controller.MediaEncoding
break;
case Video3DFormat.FullSideBySide:
filter = "crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2";
- //fsbs crop width in half,set the display aspect,crop out any black bars we may have made the scale width to requestedWidth.
+ // fsbs crop width in half,set the display aspect,crop out any black bars we may have made the scale width to requestedWidth.
break;
case Video3DFormat.HalfTopAndBottom:
filter = "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2";
- //htab crop height in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to requestedWidth
+ // htab crop height in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to requestedWidth
break;
case Video3DFormat.FullTopAndBottom:
filter = "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2";
@@ -1965,7 +2016,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// If we're going to put a fixed size on the command line, this will calculate it
+ /// If we're going to put a fixed size on the command line, this will calculate it.
/// </summary>
public string GetOutputSizeParam(
EncodingJobInfo state,
@@ -1979,13 +2030,11 @@ namespace MediaBrowser.Controller.MediaEncoding
var videoStream = state.VideoStream;
var filters = new List<string>();
- var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options);
+ var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty;
var inputWidth = videoStream?.Width;
var inputHeight = videoStream?.Height;
var threeDFormat = state.MediaSource.Video3DFormat;
- var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
-
// When the input may or may not be hardware VAAPI decodable
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
@@ -1996,20 +2045,16 @@ namespace MediaBrowser.Controller.MediaEncoding
// When the input may or may not be hardware QSV decodable
else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
- if (!hasTextSubs)
- {
- filters.Add("format=nv12|qsv");
- filters.Add("hwupload=extra_hw_frames=64");
- }
+ filters.Add("format=nv12|qsv");
+ filters.Add("hwupload=extra_hw_frames=64");
}
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
- else if (IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
+ else if (IsVaapiSupported(state) && videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1
&& string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
{
var codec = videoStream.Codec.ToLowerInvariant();
- var isColorDepth10 = !string.IsNullOrEmpty(videoStream.Profile) && (videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase)
- || videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase));
+ var isColorDepth10 = IsColorDepth10(state);
// Assert 10-bit hardware VAAPI decodable
if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
@@ -2040,31 +2085,32 @@ namespace MediaBrowser.Controller.MediaEncoding
{
filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_vaapi"));
}
- else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
- {
- if (!hasTextSubs)
- {
- filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_qsv"));
- }
- }
}
// Add software deinterlace filter before scaling filter
- if (((state.DeInterlace("h264", true) || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true))
- && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
- && !string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
- || (hasTextSubs && state.DeInterlace("h264", true) && string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)))
+ if (state.DeInterlace("h264", true) || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true))
{
+ var deintParam = string.Empty;
var inputFramerate = videoStream?.RealFrameRate;
// If it is already 60fps then it will create an output framerate that is much too high for roku and others to handle
if (string.Equals(options.DeinterlaceMethod, "yadif_bob", StringComparison.OrdinalIgnoreCase) && (inputFramerate ?? 60) <= 30)
{
- filters.Add("yadif=1:-1:0");
+ deintParam = "yadif=1:-1:0";
}
else
{
- filters.Add("yadif=0:-1:0");
+ deintParam = "yadif=0:-1:0";
+ }
+
+ if (!string.IsNullOrEmpty(deintParam))
+ {
+ if (!string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
+ && !string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
+ && videoDecoder.IndexOf("h264_cuvid", StringComparison.OrdinalIgnoreCase) == -1)
+ {
+ filters.Add(deintParam);
+ }
}
}
@@ -2250,7 +2296,7 @@ namespace MediaBrowser.Controller.MediaEncoding
flags.Add("+ignidx");
}
- if (state.GenPtsInput || EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
+ if (state.GenPtsInput || IsCopyCodec(state.OutputVideoCodec))
{
flags.Add("+genpts");
}
@@ -2276,7 +2322,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
inputModifier += " " + videoDecoder;
- if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1)
+ if (!IsCopyCodec(state.OutputVideoCodec)
+ && (videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1)
{
var videoStream = state.VideoStream;
var inputWidth = videoStream?.Width;
@@ -2289,11 +2336,19 @@ namespace MediaBrowser.Controller.MediaEncoding
&& width.HasValue
&& height.HasValue)
{
- inputModifier += string.Format(
- CultureInfo.InvariantCulture,
- " -resize {0}x{1}",
- width.Value,
- height.Value);
+ if (width.HasValue && height.HasValue)
+ {
+ inputModifier += string.Format(
+ CultureInfo.InvariantCulture,
+ " -resize {0}x{1}",
+ width.Value,
+ height.Value);
+ }
+
+ if (state.DeInterlace("h264", true))
+ {
+ inputModifier += " -deint 1";
+ }
}
}
}
@@ -2509,20 +2564,21 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Gets the name of the output video codec
+ /// Gets the ffmpeg option string for the hardware accelerated video decoder.
/// </summary>
+ /// <param name="state">The encoding job info.</param>
+ /// <param name="encodingOptions">The encoding options.</param>
+ /// <returns>The option string or null if none available.</returns>
protected string GetHardwareAcceleratedVideoDecoder(EncodingJobInfo state, EncodingOptions encodingOptions)
{
- if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
+ var videoStream = state.VideoStream;
+
+ if (videoStream == null)
{
return null;
}
- return GetHardwareAcceleratedVideoDecoder(state.MediaSource.VideoType ?? VideoType.VideoFile, state.VideoStream, encodingOptions);
- }
-
- public string GetHardwareAcceleratedVideoDecoder(VideoType videoType, MediaStream videoStream, EncodingOptions encodingOptions)
- {
+ var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile;
// Only use alternative encoders for video files.
// When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
// Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
@@ -2531,10 +2587,23 @@ namespace MediaBrowser.Controller.MediaEncoding
return null;
}
- if (videoStream != null
- && !string.IsNullOrEmpty(videoStream.Codec)
- && !string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType))
+ if (IsCopyCodec(state.OutputVideoCodec))
{
+ return null;
+ }
+
+ if (!string.IsNullOrEmpty(videoStream.Codec) && !string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType))
+ {
+ var isColorDepth10 = IsColorDepth10(state);
+
+ // Only hevc and vp9 formats have 10-bit hardware decoder support now.
+ if (isColorDepth10 && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase)))
+ {
+ return null;
+ }
+
if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
{
switch (videoStream.Codec.ToLowerInvariant())
@@ -2549,32 +2618,51 @@ namespace MediaBrowser.Controller.MediaEncoding
encodingOptions.HardwareDecodingCodecs = Array.Empty<string>();
return null;
}
+
return "-c:v h264_qsv";
}
+
break;
case "hevc":
case "h265":
if (_mediaEncoder.SupportsDecoder("hevc_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase))
{
- //return "-c:v hevc_qsv -load_plugin hevc_hw ";
- return "-c:v hevc_qsv";
+ return (isColorDepth10 &&
+ !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : "-c:v hevc_qsv";
}
+
break;
case "mpeg2video":
if (_mediaEncoder.SupportsDecoder("mpeg2_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
{
return "-c:v mpeg2_qsv";
}
+
break;
case "vc1":
if (_mediaEncoder.SupportsDecoder("vc1_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
{
return "-c:v vc1_qsv";
}
+
+ break;
+ case "vp8":
+ if (_mediaEncoder.SupportsDecoder("vp8_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase))
+ {
+ return "-c:v vp8_qsv";
+ }
+
+ break;
+ case "vp9":
+ if (_mediaEncoder.SupportsDecoder("vp9_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase))
+ {
+ return (isColorDepth10 &&
+ !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : "-c:v vp9_qsv";
+ }
+
break;
}
}
-
else if (string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
{
switch (videoStream.Codec.ToLowerInvariant())
@@ -2583,43 +2671,57 @@ namespace MediaBrowser.Controller.MediaEncoding
case "h264":
if (_mediaEncoder.SupportsDecoder("h264_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
{
- // cuvid decoder does not support 10-bit input
- if ((videoStream.BitDepth ?? 8) > 8)
- {
- encodingOptions.HardwareDecodingCodecs = Array.Empty<string>();
- return null;
- }
return "-c:v h264_cuvid";
}
+
break;
case "hevc":
case "h265":
if (_mediaEncoder.SupportsDecoder("hevc_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase))
{
- return "-c:v hevc_cuvid";
+ return (isColorDepth10 &&
+ !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : "-c:v hevc_cuvid";
}
+
break;
case "mpeg2video":
if (_mediaEncoder.SupportsDecoder("mpeg2_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
{
return "-c:v mpeg2_cuvid";
}
+
break;
case "vc1":
if (_mediaEncoder.SupportsDecoder("vc1_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
{
return "-c:v vc1_cuvid";
}
+
break;
case "mpeg4":
if (_mediaEncoder.SupportsDecoder("mpeg4_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase))
{
return "-c:v mpeg4_cuvid";
}
+
+ break;
+ case "vp8":
+ if (_mediaEncoder.SupportsDecoder("vp8_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase))
+ {
+ return "-c:v vp8_cuvid";
+ }
+
+ break;
+ case "vp9":
+ if (_mediaEncoder.SupportsDecoder("vp9_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase))
+ {
+ return (isColorDepth10 &&
+ !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : "-c:v vp9_cuvid";
+ }
+
break;
}
}
-
else if (string.Equals(encodingOptions.HardwareAccelerationType, "mediacodec", StringComparison.OrdinalIgnoreCase))
{
switch (videoStream.Codec.ToLowerInvariant())
@@ -2630,41 +2732,48 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return "-c:v h264_mediacodec";
}
+
break;
case "hevc":
case "h265":
if (_mediaEncoder.SupportsDecoder("hevc_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase))
{
- return "-c:v hevc_mediacodec";
+ return (isColorDepth10 &&
+ !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : "-c:v hevc_mediacodec";
}
+
break;
case "mpeg2video":
if (_mediaEncoder.SupportsDecoder("mpeg2_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
{
return "-c:v mpeg2_mediacodec";
}
+
break;
case "mpeg4":
if (_mediaEncoder.SupportsDecoder("mpeg4_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase))
{
return "-c:v mpeg4_mediacodec";
}
+
break;
case "vp8":
if (_mediaEncoder.SupportsDecoder("vp8_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase))
{
return "-c:v vp8_mediacodec";
}
+
break;
case "vp9":
if (_mediaEncoder.SupportsDecoder("vp9_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase))
{
- return "-c:v vp9_mediacodec";
+ return (isColorDepth10 &&
+ !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : "-c:v vp9_mediacodec";
}
+
break;
}
}
-
else if (string.Equals(encodingOptions.HardwareAccelerationType, "omx", StringComparison.OrdinalIgnoreCase))
{
switch (videoStream.Codec.ToLowerInvariant())
@@ -2675,51 +2784,185 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return "-c:v h264_mmal";
}
+
break;
case "mpeg2video":
if (_mediaEncoder.SupportsDecoder("mpeg2_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
{
return "-c:v mpeg2_mmal";
}
+
break;
case "mpeg4":
if (_mediaEncoder.SupportsDecoder("mpeg4_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase))
{
return "-c:v mpeg4_mmal";
}
+
break;
case "vc1":
if (_mediaEncoder.SupportsDecoder("vc1_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
{
return "-c:v vc1_mmal";
}
+
break;
}
}
-
else if (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase))
{
- if (Environment.OSVersion.Platform == PlatformID.Win32NT)
+ switch (videoStream.Codec.ToLowerInvariant())
{
- if (Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1))
- return "-hwaccel d3d11va";
- else
- return "-hwaccel dxva2";
+ case "avc":
+ case "h264":
+ return GetHwaccelType(state, encodingOptions, "h264");
+ case "hevc":
+ case "h265":
+ return (isColorDepth10 &&
+ !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : GetHwaccelType(state, encodingOptions, "hevc");
+ case "mpeg2video":
+ return GetHwaccelType(state, encodingOptions, "mpeg2video");
+ case "vc1":
+ return GetHwaccelType(state, encodingOptions, "vc1");
+ case "mpeg4":
+ return GetHwaccelType(state, encodingOptions, "mpeg4");
+ case "vp9":
+ return (isColorDepth10 &&
+ !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : GetHwaccelType(state, encodingOptions, "vp9");
}
- else
+ }
+ else if (string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ switch (videoStream.Codec.ToLowerInvariant())
{
- return "-hwaccel vaapi";
+ case "avc":
+ case "h264":
+ return GetHwaccelType(state, encodingOptions, "h264");
+ case "hevc":
+ case "h265":
+ return (isColorDepth10 &&
+ !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : GetHwaccelType(state, encodingOptions, "hevc");
+ case "mpeg2video":
+ return GetHwaccelType(state, encodingOptions, "mpeg2video");
+ case "vc1":
+ return GetHwaccelType(state, encodingOptions, "vc1");
+ case "vp8":
+ return GetHwaccelType(state, encodingOptions, "vp8");
+ case "vp9":
+ return (isColorDepth10 &&
+ !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : GetHwaccelType(state, encodingOptions, "vp9");
+ }
+ }
+ else if (string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
+ {
+ switch (videoStream.Codec.ToLowerInvariant())
+ {
+ case "avc":
+ case "h264":
+ if (_mediaEncoder.SupportsDecoder("h264_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
+ {
+ return "-c:v h264_opencl";
+ }
+
+ break;
+ case "hevc":
+ case "h265":
+ if (_mediaEncoder.SupportsDecoder("hevc_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
+ {
+ return (isColorDepth10 &&
+ !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : "-c:v hevc_opencl";
+ }
+
+ break;
+ case "mpeg2video":
+ if (_mediaEncoder.SupportsDecoder("mpeg2_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
+ {
+ return "-c:v mpeg2_opencl";
+ }
+
+ break;
+ case "mpeg4":
+ if (_mediaEncoder.SupportsDecoder("mpeg4_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase))
+ {
+ return "-c:v mpeg4_opencl";
+ }
+
+ break;
+ case "vc1":
+ if (_mediaEncoder.SupportsDecoder("vc1_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
+ {
+ return "-c:v vc1_opencl";
+ }
+
+ break;
+ case "vp8":
+ if (_mediaEncoder.SupportsDecoder("vp8_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
+ {
+ return "-c:v vp8_opencl";
+ }
+
+ break;
+ case "vp9":
+ if (_mediaEncoder.SupportsDecoder("vp9_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
+ {
+ return (isColorDepth10 &&
+ !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : "-c:v vp9_opencl";
+ }
+
+ break;
}
}
}
+ var whichCodec = videoStream.Codec.ToLowerInvariant();
+ switch (whichCodec)
+ {
+ case "avc":
+ whichCodec = "h264";
+ break;
+ case "h265":
+ whichCodec = "hevc";
+ break;
+ }
+
// Avoid a second attempt if no hardware acceleration is being used
- encodingOptions.HardwareDecodingCodecs = Array.Empty<string>();
+ encodingOptions.HardwareDecodingCodecs = encodingOptions.HardwareDecodingCodecs.Where(val => val != whichCodec).ToArray();
// leave blank so ffmpeg will decide
return null;
}
+ /// <summary>
+ /// Gets a hwaccel type to use as a hardware decoder(dxva/vaapi) depending on the system
+ /// </summary>
+ public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec)
+ {
+ var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
+ var isWindows8orLater = Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1);
+ var isDxvaSupported = _mediaEncoder.SupportsHwaccel("dxva2") || _mediaEncoder.SupportsHwaccel("d3d11va");
+
+ if ((isDxvaSupported || IsVaapiSupported(state)) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase))
+ {
+ if (isLinux)
+ {
+ return "-hwaccel vaapi";
+ }
+
+ if (isWindows && isWindows8orLater)
+ {
+ return "-hwaccel d3d11va";
+ }
+
+ if (isWindows && !isWindows8orLater)
+ {
+ return "-hwaccel dxva2";
+ }
+ }
+
+ return null;
+ }
+
public string GetSubtitleEmbedArguments(EncodingJobInfo state)
{
if (state.SubtitleStream == null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
@@ -2801,7 +3044,7 @@ namespace MediaBrowser.Controller.MediaEncoding
args += " -mpegts_m2ts_mode 1";
}
- if (EncodingHelper.IsCopyCodec(videoCodec))
+ if (IsCopyCodec(videoCodec))
{
if (state.VideoStream != null
&& string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
@@ -2903,7 +3146,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var args = "-codec:a:0 " + codec;
- if (EncodingHelper.IsCopyCodec(codec))
+ if (IsCopyCodec(codec))
{
return args;
}
@@ -2980,5 +3223,42 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
}
+
+ public static bool IsColorDepth10(EncodingJobInfo state)
+ {
+ var result = false;
+ var videoStream = state.VideoStream;
+
+ if (videoStream != null)
+ {
+ if (!string.IsNullOrEmpty(videoStream.PixelFormat))
+ {
+ result = videoStream.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase);
+ if (result)
+ {
+ return true;
+ }
+ }
+
+ if (!string.IsNullOrEmpty(videoStream.Profile))
+ {
+ result = videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase)
+ || videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase)
+ || videoStream.Profile.Contains("Profile 2", StringComparison.OrdinalIgnoreCase);
+ if (result)
+ {
+ return true;
+ }
+ }
+
+ result = (videoStream.BitDepth ?? 8) == 10;
+ if (result)
+ {
+ return true;
+ }
+ }
+
+ return result;
+ }
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
index acf1aae89..b971b7c4b 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
@@ -127,13 +127,19 @@ namespace MediaBrowser.Controller.MediaEncoding
public string AlbumCoverPath { get; set; }
public string InputAudioSync { get; set; }
+
public string InputVideoSync { get; set; }
+
public TransportStreamTimestamp InputTimestamp { get; set; }
public MediaStream AudioStream { get; set; }
+
public string[] SupportedAudioCodecs { get; set; }
+
public string[] SupportedVideoCodecs { get; set; }
+
public string InputContainer { get; set; }
+
public IsoType? IsoType { get; set; }
public BaseEncodingJobOptions BaseRequest { get; set; }
@@ -293,6 +299,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
public bool IsVideoRequest { get; set; }
+
public TranscodingJobType TranscodingType { get; set; }
public EncodingJobInfo(TranscodingJobType jobType)
@@ -418,7 +425,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Predicts the audio sample rate that will be in the output stream
+ /// Predicts the audio sample rate that will be in the output stream.
/// </summary>
public double? TargetVideoLevel
{
@@ -441,7 +448,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Predicts the audio sample rate that will be in the output stream
+ /// Predicts the audio sample rate that will be in the output stream.
/// </summary>
public int? TargetVideoBitDepth
{
@@ -476,7 +483,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Predicts the audio sample rate that will be in the output stream
+ /// Predicts the audio sample rate that will be in the output stream.
/// </summary>
public float? TargetFramerate
{
@@ -508,7 +515,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Predicts the audio sample rate that will be in the output stream
+ /// Predicts the audio sample rate that will be in the output stream.
/// </summary>
public int? TargetPacketLength
{
@@ -524,7 +531,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Predicts the audio sample rate that will be in the output stream
+ /// Predicts the audio sample rate that will be in the output stream.
/// </summary>
public string TargetVideoProfile
{
@@ -672,6 +679,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
public IProgress<double> Progress { get; set; }
+
public virtual void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
{
Progress.Report(percentComplete.Value);
@@ -679,20 +687,20 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Enum TranscodingJobType
+ /// Enum TranscodingJobType.
/// </summary>
public enum TranscodingJobType
{
/// <summary>
- /// The progressive
+ /// The progressive.
/// </summary>
Progressive,
/// <summary>
- /// The HLS
+ /// The HLS.
/// </summary>
Hls,
/// <summary>
- /// The dash
+ /// The dash.
/// </summary>
Dash
}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
index addc88174..8f6fcb9ab 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
@@ -9,9 +9,11 @@ namespace MediaBrowser.Controller.MediaEncoding
public class EncodingJobOptions : BaseEncodingJobOptions
{
public string OutputDirectory { get; set; }
+
public string ItemId { get; set; }
public string TempDirectory { get; set; }
+
public bool ReadInputAtNativeFramerate { get; set; }
/// <summary>
@@ -47,6 +49,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
SubtitleStreamIndex = info.SubtitleStreamIndex;
}
+
StreamOptions = info.StreamOptions;
}
}
@@ -81,7 +84,9 @@ namespace MediaBrowser.Controller.MediaEncoding
public bool EnableAutoStreamCopy { get; set; }
public bool AllowVideoStreamCopy { get; set; }
+
public bool AllowAudioStreamCopy { get; set; }
+
public bool BreakOnNonKeyFrames { get; set; }
/// <summary>
@@ -197,10 +202,15 @@ namespace MediaBrowser.Controller.MediaEncoding
[ApiMember(Name = "MaxVideoBitDepth", Description = "Optional.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? MaxVideoBitDepth { get; set; }
+
public bool RequireAvc { get; set; }
+
public bool DeInterlace { get; set; }
+
public bool RequireNonAnamorphic { get; set; }
+
public int? TranscodingMaxAudioChannels { get; set; }
+
public int? CpuCoreLimit { get; set; }
public string LiveStreamId { get; set; }
diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
index 37f0b11a7..f60e70239 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -11,7 +11,7 @@ using MediaBrowser.Model.System;
namespace MediaBrowser.Controller.MediaEncoding
{
/// <summary>
- /// Interface IMediaEncoder
+ /// Interface IMediaEncoder.
/// </summary>
public interface IMediaEncoder : ITranscoderSupport
{
@@ -27,13 +27,27 @@ namespace MediaBrowser.Controller.MediaEncoding
string EncoderPath { get; }
/// <summary>
- /// Supportses the decoder.
+ /// Whether given encoder codec is supported.
+ /// </summary>
+ /// <param name="encoder">The encoder.</param>
+ /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
+ bool SupportsEncoder(string encoder);
+
+ /// <summary>
+ /// Whether given decoder codec is supported.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
bool SupportsDecoder(string decoder);
/// <summary>
+ /// Whether given hardware acceleration type is supported.
+ /// </summary>
+ /// <param name="hwaccel">The hwaccel.</param>
+ /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
+ bool SupportsHwaccel(string hwaccel);
+
+ /// <summary>
/// Extracts the audio image.
/// </summary>
/// <param name="path">The path.</param>
@@ -98,7 +112,6 @@ namespace MediaBrowser.Controller.MediaEncoding
void SetFFmpegPath();
void UpdateEncoderPath(string path, string pathType);
- bool SupportsEncoder(string encoder);
IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, IIsoMount isoMount, uint? titleNumber);
}
diff --git a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs
index 5cedc3d57..6c9bbb043 100644
--- a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs
+++ b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs
@@ -7,7 +7,7 @@ using MediaBrowser.Model.IO;
namespace MediaBrowser.Controller.MediaEncoding
{
/// <summary>
- /// Class MediaEncoderHelpers
+ /// Class MediaEncoderHelpers.
/// </summary>
public static class MediaEncoderHelpers
{
diff --git a/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs b/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs
index b78ef0b80..39a47792a 100644
--- a/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs
+++ b/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs
@@ -8,9 +8,13 @@ namespace MediaBrowser.Controller.MediaEncoding
public class MediaInfoRequest
{
public MediaSourceInfo MediaSource { get; set; }
+
public bool ExtractChapters { get; set; }
+
public DlnaProfileType MediaType { get; set; }
+
public IIsoMount MountedIso { get; set; }
+
public string[] PlayableStreamFileNames { get; set; }
public MediaInfoRequest()
diff --git a/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs
index 9f2743ea1..87a7f7e10 100644
--- a/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs
+++ b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs
@@ -31,9 +31,9 @@ namespace MediaBrowser.Controller.Net
/// <summary>
/// The request filter is executed before the service.
/// </summary>
- /// <param name="request">The http request wrapper</param>
- /// <param name="response">The http response wrapper</param>
- /// <param name="requestDto">The request DTO</param>
+ /// <param name="request">The http request wrapper.</param>
+ /// <param name="response">The http response wrapper.</param>
+ /// <param name="requestDto">The request DTO.</param>
public void RequestFilter(IRequest request, HttpResponse response, object requestDto)
{
AuthService.Authenticate(request, this);
@@ -60,8 +60,11 @@ namespace MediaBrowser.Controller.Net
public interface IAuthenticationAttributes
{
bool EscapeParentalControl { get; }
+
bool AllowBeforeStartupWizard { get; }
+
bool AllowLocal { get; }
+
bool AllowLocalOnly { get; }
string[] GetRoles();
diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
index f09cb3705..a54f6d57b 100644
--- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
+++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
@@ -11,7 +11,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Net
{
/// <summary>
- /// Starts sending data over a web socket periodically when a message is received, and then stops when a corresponding stop message is received
+ /// Starts sending data over a web socket periodically when a message is received, and then stops when a corresponding stop message is received.
/// </summary>
/// <typeparam name="TReturnDataType">The type of the T return data type.</typeparam>
/// <typeparam name="TStateType">The type of the T state type.</typeparam>
@@ -20,7 +20,7 @@ namespace MediaBrowser.Controller.Net
where TReturnDataType : class
{
/// <summary>
- /// The _active connections
+ /// The _active connections.
/// </summary>
private readonly List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>> _activeConnections =
new List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>>();
@@ -38,7 +38,7 @@ namespace MediaBrowser.Controller.Net
protected abstract Task<TReturnDataType> GetDataToSend();
/// <summary>
- /// The logger
+ /// The logger.
/// </summary>
protected ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> Logger;
@@ -78,7 +78,7 @@ namespace MediaBrowser.Controller.Net
}
/// <summary>
- /// Starts sending messages over a web socket
+ /// Starts sending messages over a web socket.
/// </summary>
/// <param name="message">The message.</param>
private void Start(WebSocketMessageInfo message)
@@ -180,7 +180,7 @@ namespace MediaBrowser.Controller.Net
}
/// <summary>
- /// Stops sending messages over a web socket
+ /// Stops sending messages over a web socket.
/// </summary>
/// <param name="message">The message.</param>
private void Stop(WebSocketMessageInfo message)
@@ -214,7 +214,7 @@ namespace MediaBrowser.Controller.Net
}
catch (ObjectDisposedException)
{
- //TODO Investigate and properly fix.
+ // TODO Investigate and properly fix.
}
lock (_activeConnections)
@@ -254,7 +254,9 @@ namespace MediaBrowser.Controller.Net
public class WebSocketListenerState
{
public DateTime DateLastSendUtc { get; set; }
+
public long InitialDelayMs { get; set; }
+
public long IntervalMs { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Net/IHttpResultFactory.cs b/MediaBrowser.Controller/Net/IHttpResultFactory.cs
index 25404fa78..609bd5f59 100644
--- a/MediaBrowser.Controller/Net/IHttpResultFactory.cs
+++ b/MediaBrowser.Controller/Net/IHttpResultFactory.cs
@@ -7,7 +7,7 @@ using MediaBrowser.Model.Services;
namespace MediaBrowser.Controller.Net
{
/// <summary>
- /// Interface IHttpResultFactory
+ /// Interface IHttpResultFactory.
/// </summary>
public interface IHttpResultFactory
{
diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs
index efb5f4ac3..e6609fae3 100644
--- a/MediaBrowser.Controller/Net/IHttpServer.cs
+++ b/MediaBrowser.Controller/Net/IHttpServer.cs
@@ -29,19 +29,19 @@ namespace MediaBrowser.Controller.Net
void Init(IEnumerable<Type> serviceTypes, IEnumerable<IWebSocketListener> listener, IEnumerable<string> urlPrefixes);
/// <summary>
- /// If set, all requests will respond with this message
+ /// If set, all requests will respond with this message.
/// </summary>
string GlobalResponse { get; set; }
/// <summary>
- /// The HTTP request handler
+ /// The HTTP request handler.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
Task RequestHandler(HttpContext context);
/// <summary>
- /// Get the default CORS headers
+ /// Get the default CORS headers.
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
diff --git a/MediaBrowser.Controller/Net/IWebSocketListener.cs b/MediaBrowser.Controller/Net/IWebSocketListener.cs
index 0f472a2bc..7250a57b0 100644
--- a/MediaBrowser.Controller/Net/IWebSocketListener.cs
+++ b/MediaBrowser.Controller/Net/IWebSocketListener.cs
@@ -3,7 +3,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Controller.Net
{
/// <summary>
- ///This is an interface for listening to messages coming through a web socket connection
+ ///This is an interface for listening to messages coming through a web socket connection.
/// </summary>
public interface IWebSocketListener
{
diff --git a/MediaBrowser.Controller/Net/SecurityException.cs b/MediaBrowser.Controller/Net/SecurityException.cs
index a5b94ea5e..f0d0b45a0 100644
--- a/MediaBrowser.Controller/Net/SecurityException.cs
+++ b/MediaBrowser.Controller/Net/SecurityException.cs
@@ -27,7 +27,7 @@ namespace MediaBrowser.Controller.Net
/// <summary>
/// Initializes a new instance of the <see cref="SecurityException"/> class.
/// </summary>
- /// <param name="message">The message that describes the error</param>
+ /// <param name="message">The message that describes the error.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
public SecurityException(string message, Exception innerException)
: base(message, innerException)
diff --git a/MediaBrowser.Controller/Net/StaticResultOptions.cs b/MediaBrowser.Controller/Net/StaticResultOptions.cs
index 071beaed1..85772e036 100644
--- a/MediaBrowser.Controller/Net/StaticResultOptions.cs
+++ b/MediaBrowser.Controller/Net/StaticResultOptions.cs
@@ -8,8 +8,11 @@ namespace MediaBrowser.Controller.Net
public class StaticResultOptions
{
public string ContentType { get; set; }
+
public TimeSpan? CacheDuration { get; set; }
+
public DateTime? DateLastModified { get; set; }
+
public Func<Task<Stream>> ContentFactory { get; set; }
public bool IsHeadRequest { get; set; }
@@ -17,9 +20,11 @@ namespace MediaBrowser.Controller.Net
public IDictionary<string, string> ResponseHeaders { get; set; }
public Action OnComplete { get; set; }
+
public Action OnError { get; set; }
public string Path { get; set; }
+
public long? ContentLength { get; set; }
public FileShare FileShare { get; set; }
diff --git a/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs b/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs
index 5bf39cae6..be0b3ddc3 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs
@@ -3,7 +3,7 @@ using MediaBrowser.Model.Net;
namespace MediaBrowser.Controller.Net
{
/// <summary>
- /// Class WebSocketMessageInfo
+ /// Class WebSocketMessageInfo.
/// </summary>
public class WebSocketMessageInfo : WebSocketMessage<string>
{
diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs
index 75fc43a04..0ae1b8bbf 100644
--- a/MediaBrowser.Controller/Persistence/IItemRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs
@@ -9,12 +9,12 @@ using MediaBrowser.Model.Querying;
namespace MediaBrowser.Controller.Persistence
{
/// <summary>
- /// Provides an interface to implement an Item repository
+ /// Provides an interface to implement an Item repository.
/// </summary>
public interface IItemRepository : IRepository
{
/// <summary>
- /// Saves an item
+ /// Saves an item.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="cancellationToken">The cancellation token.</param>
@@ -43,14 +43,14 @@ namespace MediaBrowser.Controller.Persistence
BaseItem RetrieveItem(Guid id);
/// <summary>
- /// Gets chapters for an item
+ /// Gets chapters for an item.
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
List<ChapterInfo> GetChapters(BaseItem id);
/// <summary>
- /// Gets a single chapter for an item
+ /// Gets a single chapter for an item.
/// </summary>
/// <param name="id"></param>
/// <param name="index"></param>
diff --git a/MediaBrowser.Controller/Persistence/IRepository.cs b/MediaBrowser.Controller/Persistence/IRepository.cs
index 56bf1dd5a..42f285076 100644
--- a/MediaBrowser.Controller/Persistence/IRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IRepository.cs
@@ -3,12 +3,12 @@ using System;
namespace MediaBrowser.Controller.Persistence
{
/// <summary>
- /// Provides a base interface for all the repository interfaces
+ /// Provides a base interface for all the repository interfaces.
/// </summary>
public interface IRepository : IDisposable
{
/// <summary>
- /// Gets the name of the repository
+ /// Gets the name of the repository.
/// </summary>
/// <value>The name.</value>
string Name { get; }
diff --git a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs
index a4bdf60d7..ba7c9fd50 100644
--- a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs
@@ -5,7 +5,7 @@ using MediaBrowser.Controller.Entities;
namespace MediaBrowser.Controller.Persistence
{
/// <summary>
- /// Provides an interface to implement a UserData repository
+ /// Provides an interface to implement a UserData repository.
/// </summary>
public interface IUserDataRepository : IRepository
{
@@ -30,20 +30,19 @@ namespace MediaBrowser.Controller.Persistence
UserItemData GetUserData(long userId, List<string> keys);
/// <summary>
- /// Return all user data associated with the given user
+ /// Return all user data associated with the given user.
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
List<UserItemData> GetAllUserData(long userId);
/// <summary>
- /// Save all user data associated with the given user
+ /// Save all user data associated with the given user.
/// </summary>
/// <param name="userId"></param>
/// <param name="userData"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
void SaveAllUserData(long userId, UserItemData[] userData, CancellationToken cancellationToken);
-
}
}
diff --git a/MediaBrowser.Controller/Plugins/IPluginConfigurationPage.cs b/MediaBrowser.Controller/Plugins/IPluginConfigurationPage.cs
index c156da924..077f5ab63 100644
--- a/MediaBrowser.Controller/Plugins/IPluginConfigurationPage.cs
+++ b/MediaBrowser.Controller/Plugins/IPluginConfigurationPage.cs
@@ -4,7 +4,7 @@ using MediaBrowser.Common.Plugins;
namespace MediaBrowser.Controller.Plugins
{
/// <summary>
- /// Interface IConfigurationPage
+ /// Interface IConfigurationPage.
/// </summary>
public interface IPluginConfigurationPage
{
@@ -34,16 +34,16 @@ namespace MediaBrowser.Controller.Plugins
}
/// <summary>
- /// Enum ConfigurationPageType
+ /// Enum ConfigurationPageType.
/// </summary>
public enum ConfigurationPageType
{
/// <summary>
- /// The plugin configuration
+ /// The plugin configuration.
/// </summary>
PluginConfiguration,
/// <summary>
- /// The none
+ /// The none.
/// </summary>
None
}
diff --git a/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs b/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs
index 1e8654c4d..b44e2531e 100644
--- a/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs
+++ b/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs
@@ -22,6 +22,5 @@ namespace MediaBrowser.Controller.Plugins
/// </summary>
public interface IRunBeforeStartup
{
-
}
}
diff --git a/MediaBrowser.Controller/Providers/IExternalId.cs b/MediaBrowser.Controller/Providers/IExternalId.cs
index d7e337bda..5e38446bc 100644
--- a/MediaBrowser.Controller/Providers/IExternalId.cs
+++ b/MediaBrowser.Controller/Providers/IExternalId.cs
@@ -1,15 +1,45 @@
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
namespace MediaBrowser.Controller.Providers
{
+ /// <summary>
+ /// Represents an identifier for an external provider.
+ /// </summary>
public interface IExternalId
{
- string Name { get; }
+ /// <summary>
+ /// Gets the display name of the provider associated with this ID type.
+ /// </summary>
+ string ProviderName { get; }
+ /// <summary>
+ /// Gets the unique key to distinguish this provider/type pair. This should be unique across providers.
+ /// </summary>
+ // TODO: This property is not actually unique across the concrete types at the moment. It should be updated to be unique.
string Key { get; }
+ /// <summary>
+ /// Gets the specific media type for this id. This is used to distinguish between the different
+ /// external id types for providers with multiple ids.
+ /// A null value indicates there is no specific media type associated with the external id, or this is the
+ /// default id for the external provider so there is no need to specify a type.
+ /// </summary>
+ /// <remarks>
+ /// This can be used along with the <see cref="ProviderName"/> to localize the external id on the client.
+ /// </remarks>
+ ExternalIdMediaType? Type { get; }
+
+ /// <summary>
+ /// Gets the URL format string for this id.
+ /// </summary>
string UrlFormatString { get; }
+ /// <summary>
+ /// Determines whether this id supports a given item type.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <returns>True if this item is supported, otherwise false.</returns>
bool Supports(IHasProviderIds item);
}
}
diff --git a/MediaBrowser.Controller/Providers/IMetadataProvider.cs b/MediaBrowser.Controller/Providers/IMetadataProvider.cs
index 3e595ff93..62b16dadd 100644
--- a/MediaBrowser.Controller/Providers/IMetadataProvider.cs
+++ b/MediaBrowser.Controller/Providers/IMetadataProvider.cs
@@ -3,7 +3,7 @@ using MediaBrowser.Controller.Entities;
namespace MediaBrowser.Controller.Providers
{
/// <summary>
- /// Marker interface
+ /// Marker interface.
/// </summary>
public interface IMetadataProvider
{
diff --git a/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs b/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs
index aac41369c..3f8c409f5 100644
--- a/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs
+++ b/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs
@@ -7,11 +7,13 @@ namespace MediaBrowser.Controller.Providers
public class ImageRefreshOptions
{
public MetadataRefreshMode ImageRefreshMode { get; set; }
+
public IDirectoryService DirectoryService { get; private set; }
public bool ReplaceAllImages { get; set; }
public ImageType[] ReplaceImages { get; set; }
+
public bool IsAutomated { get; set; }
public ImageRefreshOptions(IDirectoryService directoryService)
diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshMode.cs b/MediaBrowser.Controller/Providers/MetadataRefreshMode.cs
index 02152ee33..6d49b5510 100644
--- a/MediaBrowser.Controller/Providers/MetadataRefreshMode.cs
+++ b/MediaBrowser.Controller/Providers/MetadataRefreshMode.cs
@@ -3,22 +3,22 @@ namespace MediaBrowser.Controller.Providers
public enum MetadataRefreshMode
{
/// <summary>
- /// The none
+ /// The none.
/// </summary>
None = 0,
/// <summary>
- /// The validation only
+ /// The validation only.
/// </summary>
ValidationOnly = 1,
/// <summary>
- /// Providers will be executed based on default rules
+ /// Providers will be executed based on default rules.
/// </summary>
Default = 2,
/// <summary>
- /// All providers will be executed to search for new metadata
+ /// All providers will be executed to search for new metadata.
/// </summary>
FullRefresh = 3
}
diff --git a/MediaBrowser.Controller/Providers/MetadataResult.cs b/MediaBrowser.Controller/Providers/MetadataResult.cs
index 59adaedfa..270ea2444 100644
--- a/MediaBrowser.Controller/Providers/MetadataResult.cs
+++ b/MediaBrowser.Controller/Providers/MetadataResult.cs
@@ -40,7 +40,7 @@ namespace MediaBrowser.Controller.Providers
}
/// <summary>
- /// Not only does this clear, but initializes the list so that services can differentiate between a null list and zero people
+ /// Not only does this clear, but initializes the list so that services can differentiate between a null list and zero people.
/// </summary>
public void ResetPeople()
{
@@ -48,6 +48,7 @@ namespace MediaBrowser.Controller.Providers
{
People = new List<PersonInfo>();
}
+
People.Clear();
}
diff --git a/MediaBrowser.Controller/Providers/VideoContentType.cs b/MediaBrowser.Controller/Providers/VideoContentType.cs
index c3b8964a3..49d587f6c 100644
--- a/MediaBrowser.Controller/Providers/VideoContentType.cs
+++ b/MediaBrowser.Controller/Providers/VideoContentType.cs
@@ -1,17 +1,17 @@
namespace MediaBrowser.Controller.Providers
{
/// <summary>
- /// Enum VideoContentType
+ /// Enum VideoContentType.
/// </summary>
public enum VideoContentType
{
/// <summary>
- /// The episode
+ /// The episode.
/// </summary>
Episode = 0,
/// <summary>
- /// The movie
+ /// The movie.
/// </summary>
Movie = 1
}
diff --git a/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs
index 637a7e3f0..67acdd9a3 100644
--- a/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs
+++ b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs
@@ -4,7 +4,7 @@ using MediaBrowser.Controller.Library;
namespace MediaBrowser.Controller.Resolvers
{
/// <summary>
- /// Class ItemResolver
+ /// Class ItemResolver.
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class ItemResolver<T> : IItemResolver
@@ -27,7 +27,7 @@ namespace MediaBrowser.Controller.Resolvers
public virtual ResolverPriority Priority => ResolverPriority.First;
/// <summary>
- /// Sets initial values on the newly resolved item
+ /// Sets initial values on the newly resolved item.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>
diff --git a/MediaBrowser.Controller/Resolvers/IItemResolver.cs b/MediaBrowser.Controller/Resolvers/IItemResolver.cs
index 16e37d249..a73937b3e 100644
--- a/MediaBrowser.Controller/Resolvers/IItemResolver.cs
+++ b/MediaBrowser.Controller/Resolvers/IItemResolver.cs
@@ -7,7 +7,7 @@ using MediaBrowser.Model.IO;
namespace MediaBrowser.Controller.Resolvers
{
/// <summary>
- /// Interface IItemResolver
+ /// Interface IItemResolver.
/// </summary>
public interface IItemResolver
{
@@ -35,6 +35,7 @@ namespace MediaBrowser.Controller.Resolvers
public class MultiItemResolverResult
{
public List<BaseItem> Items { get; set; }
+
public List<FileSystemMetadata> ExtraFiles { get; set; }
public MultiItemResolverResult()
diff --git a/MediaBrowser.Controller/Resolvers/ResolverPriority.cs b/MediaBrowser.Controller/Resolvers/ResolverPriority.cs
index e39310095..1911e5c1d 100644
--- a/MediaBrowser.Controller/Resolvers/ResolverPriority.cs
+++ b/MediaBrowser.Controller/Resolvers/ResolverPriority.cs
@@ -1,25 +1,25 @@
namespace MediaBrowser.Controller.Resolvers
{
/// <summary>
- /// Enum ResolverPriority
+ /// Enum ResolverPriority.
/// </summary>
public enum ResolverPriority
{
/// <summary>
- /// The first
+ /// The first.
/// </summary>
First = 1,
/// <summary>
- /// The second
+ /// The second.
/// </summary>
Second = 2,
/// <summary>
- /// The third
+ /// The third.
/// </summary>
Third = 3,
Fourth = 4,
/// <summary>
- /// The last
+ /// The last.
/// </summary>
Last = 5
}
diff --git a/MediaBrowser.Controller/Security/AuthenticationInfo.cs b/MediaBrowser.Controller/Security/AuthenticationInfo.cs
index 828213588..1d0b959b7 100644
--- a/MediaBrowser.Controller/Security/AuthenticationInfo.cs
+++ b/MediaBrowser.Controller/Security/AuthenticationInfo.cs
@@ -65,6 +65,7 @@ namespace MediaBrowser.Controller.Security
public DateTime? DateRevoked { get; set; }
public DateTime DateLastActivity { get; set; }
+
public string UserName { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Session/AuthenticationRequest.cs b/MediaBrowser.Controller/Session/AuthenticationRequest.cs
index a28f47a9c..685ca3bdd 100644
--- a/MediaBrowser.Controller/Session/AuthenticationRequest.cs
+++ b/MediaBrowser.Controller/Session/AuthenticationRequest.cs
@@ -5,13 +5,21 @@ namespace MediaBrowser.Controller.Session
public class AuthenticationRequest
{
public string Username { get; set; }
+
public Guid UserId { get; set; }
+
public string Password { get; set; }
+
public string PasswordSha1 { get; set; }
+
public string App { get; set; }
+
public string AppVersion { get; set; }
+
public string DeviceId { get; set; }
+
public string DeviceName { get; set; }
+
public string RemoteEndPoint { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs
index 1fdb588eb..e54f21050 100644
--- a/MediaBrowser.Controller/Session/ISessionManager.cs
+++ b/MediaBrowser.Controller/Session/ISessionManager.cs
@@ -13,7 +13,7 @@ using MediaBrowser.Model.SyncPlay;
namespace MediaBrowser.Controller.Session
{
/// <summary>
- /// Interface ISessionManager
+ /// Interface ISessionManager.
/// </summary>
public interface ISessionManager
{
@@ -79,14 +79,14 @@ namespace MediaBrowser.Controller.Session
void UpdateDeviceName(string sessionId, string reportedDeviceName);
/// <summary>
- /// Used to report that playback has started for an item
+ /// Used to report that playback has started for an item.
/// </summary>
/// <param name="info">The info.</param>
/// <returns>Task.</returns>
Task OnPlaybackStart(PlaybackStartInfo info);
/// <summary>
- /// Used to report playback progress for an item
+ /// Used to report playback progress for an item.
/// </summary>
/// <param name="info">The info.</param>
/// <returns>Task.</returns>
@@ -96,7 +96,7 @@ namespace MediaBrowser.Controller.Session
Task OnPlaybackProgress(PlaybackProgressInfo info, bool isAutomated);
/// <summary>
- /// Used to report that playback has ended for an item
+ /// Used to report that playback has ended for an item.
/// </summary>
/// <param name="info">The info.</param>
/// <returns>Task.</returns>
diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs
index 2ba7c9fec..4b088998c 100644
--- a/MediaBrowser.Controller/Session/SessionInfo.cs
+++ b/MediaBrowser.Controller/Session/SessionInfo.cs
@@ -61,6 +61,7 @@ namespace MediaBrowser.Controller.Session
{
return Array.Empty<string>();
}
+
return Capabilities.PlayableMediaTypes;
}
}
@@ -108,6 +109,12 @@ namespace MediaBrowser.Controller.Session
public string DeviceName { get; set; }
/// <summary>
+ /// Gets or sets the type of the device.
+ /// </summary>
+ /// <value>The type of the device.</value>
+ public string DeviceType { get; set; }
+
+ /// <summary>
/// Gets or sets the now playing item.
/// </summary>
/// <value>The now playing item.</value>
@@ -154,6 +161,7 @@ namespace MediaBrowser.Controller.Session
return true;
}
}
+
if (controllers.Length > 0)
{
return false;
@@ -213,8 +221,17 @@ namespace MediaBrowser.Controller.Session
public string PlaylistItemId { get; set; }
+ public string ServerId { get; set; }
+
public string UserPrimaryImageTag { get; set; }
+ /// <summary>
+ /// Gets or sets the supported commands.
+ /// </summary>
+ /// <value>The supported commands.</value>
+ public string[] SupportedCommands
+ => Capabilities == null ? Array.Empty<string>() : Capabilities.SupportedCommands;
+
public Tuple<ISessionController, bool> EnsureController<T>(Func<SessionInfo, ISessionController> factory)
{
var controllers = SessionControllers.ToList();
@@ -255,6 +272,7 @@ namespace MediaBrowser.Controller.Session
return true;
}
}
+
return false;
}
@@ -292,6 +310,7 @@ namespace MediaBrowser.Controller.Session
{
return;
}
+
if (progressInfo.IsPaused)
{
return;
@@ -334,6 +353,7 @@ namespace MediaBrowser.Controller.Session
_progressTimer.Dispose();
_progressTimer = null;
}
+
_lastProgressInfo = null;
}
}
diff --git a/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs b/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs
index 31087edec..727cbe639 100644
--- a/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs
+++ b/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs
@@ -4,7 +4,7 @@ using MediaBrowser.Controller.Entities;
namespace MediaBrowser.Controller.Sorting
{
/// <summary>
- /// Interface IBaseItemComparer
+ /// Interface IBaseItemComparer.
/// </summary>
public interface IBaseItemComparer : IComparer<BaseItem>
{
diff --git a/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs b/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs
index 6f75d16de..6d03d97ae 100644
--- a/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs
+++ b/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs
@@ -3,7 +3,7 @@ using MediaBrowser.Controller.Library;
namespace MediaBrowser.Controller.Sorting
{
/// <summary>
- /// Represents a BaseItem comparer that requires a User to perform it's comparison
+ /// Represents a BaseItem comparer that requires a User to perform it's comparison.
/// </summary>
public interface IUserBaseItemComparer : IBaseItemComparer
{
diff --git a/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs b/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs
index b8ba35a5f..ad6025927 100644
--- a/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs
+++ b/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs
@@ -5,8 +5,11 @@ namespace MediaBrowser.Controller.Subtitles
public class SubtitleResponse
{
public string Language { get; set; }
+
public string Format { get; set; }
+
public bool IsForced { get; set; }
+
public Stream Stream { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs
index 61dc72258..a202723b9 100644
--- a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs
+++ b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs
@@ -8,23 +8,35 @@ namespace MediaBrowser.Controller.Subtitles
public class SubtitleSearchRequest : IHasProviderIds
{
public string Language { get; set; }
+
public string TwoLetterISOLanguageName { get; set; }
public VideoContentType ContentType { get; set; }
public string MediaPath { get; set; }
+
public string SeriesName { get; set; }
+
public string Name { get; set; }
+
public int? IndexNumber { get; set; }
+
public int? IndexNumberEnd { get; set; }
+
public int? ParentIndexNumber { get; set; }
+
public int? ProductionYear { get; set; }
+
public long? RuntimeTicks { get; set; }
+
public bool IsPerfectMatch { get; set; }
+
public Dictionary<string, string> ProviderIds { get; set; }
public bool SearchAllProviders { get; set; }
+
public string[] DisabledSubtitleFetchers { get; set; }
+
public string[] SubtitleFetcherOrder { get; set; }
public SubtitleSearchRequest()
diff --git a/MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs b/MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs
index c0b62b753..b2c53365c 100644
--- a/MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs
+++ b/MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs
@@ -1,7 +1,7 @@
namespace MediaBrowser.Controller.Sync
{
/// <summary>
- /// A marker interface
+ /// A marker interface.
/// </summary>
public interface IRemoteSyncProvider
{
diff --git a/MediaBrowser.Controller/Sync/SyncedFileInfo.cs b/MediaBrowser.Controller/Sync/SyncedFileInfo.cs
index 2ff40addb..687a46d78 100644
--- a/MediaBrowser.Controller/Sync/SyncedFileInfo.cs
+++ b/MediaBrowser.Controller/Sync/SyncedFileInfo.cs
@@ -10,6 +10,7 @@ namespace MediaBrowser.Controller.Sync
/// </summary>
/// <value>The path.</value>
public string Path { get; set; }
+
public string[] PathParts { get; set; }
/// <summary>
/// Gets or sets the protocol.
diff --git a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs
index 28a3ac505..e742df517 100644
--- a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs
+++ b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs
@@ -14,9 +14,9 @@ namespace MediaBrowser.Controller.SyncPlay
public class GroupInfo
{
/// <summary>
- /// Default ping value used for sessions.
+ /// Gets the default ping value used for sessions.
/// </summary>
- public long DefaulPing { get; } = 500;
+ public long DefaultPing { get; } = 500;
/// <summary>
/// Gets or sets the group identifier.
@@ -31,13 +31,13 @@ namespace MediaBrowser.Controller.SyncPlay
public BaseItem PlayingItem { get; set; }
/// <summary>
- /// Gets or sets whether playback is paused.
+ /// Gets or sets a value indicating whether playback is paused.
/// </summary>
/// <value>Playback is paused.</value>
public bool IsPaused { get; set; }
/// <summary>
- /// Gets or sets the position ticks.
+ /// Gets or sets a value indicating whether there are position ticks.
/// </summary>
/// <value>The position ticks.</value>
public long PositionTicks { get; set; }
@@ -70,16 +70,16 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="session">The session.</param>
public void AddSession(SessionInfo session)
{
- if (ContainsSession(session.Id.ToString()))
+ if (ContainsSession(session.Id))
{
return;
}
var member = new GroupMember();
member.Session = session;
- member.Ping = DefaulPing;
+ member.Ping = DefaultPing;
member.IsBuffering = false;
- Participants[session.Id.ToString()] = member;
+ Participants[session.Id] = member;
}
/// <summary>
@@ -88,12 +88,12 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="session">The session.</param>
public void RemoveSession(SessionInfo session)
{
- if (!ContainsSession(session.Id.ToString()))
+ if (!ContainsSession(session.Id))
{
return;
}
- Participants.Remove(session.Id.ToString(), out _);
+ Participants.Remove(session.Id, out _);
}
/// <summary>
@@ -103,12 +103,12 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="ping">The ping.</param>
public void UpdatePing(SessionInfo session, long ping)
{
- if (!ContainsSession(session.Id.ToString()))
+ if (!ContainsSession(session.Id))
{
return;
}
- Participants[session.Id.ToString()].Ping = ping;
+ Participants[session.Id].Ping = ping;
}
/// <summary>
@@ -117,11 +117,12 @@ namespace MediaBrowser.Controller.SyncPlay
/// <value name="session">The highest ping in the group.</value>
public long GetHighestPing()
{
- long max = Int64.MinValue;
+ long max = long.MinValue;
foreach (var session in Participants.Values)
{
max = Math.Max(max, session.Ping);
}
+
return max;
}
@@ -132,12 +133,12 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="isBuffering">The state.</param>
public void SetBuffering(SessionInfo session, bool isBuffering)
{
- if (!ContainsSession(session.Id.ToString()))
+ if (!ContainsSession(session.Id))
{
return;
}
- Participants[session.Id.ToString()].IsBuffering = isBuffering;
+ Participants[session.Id].IsBuffering = isBuffering;
}
/// <summary>
diff --git a/MediaBrowser.Controller/SyncPlay/GroupMember.cs b/MediaBrowser.Controller/SyncPlay/GroupMember.cs
index a3975c334..cde6f8e8c 100644
--- a/MediaBrowser.Controller/SyncPlay/GroupMember.cs
+++ b/MediaBrowser.Controller/SyncPlay/GroupMember.cs
@@ -8,7 +8,7 @@ namespace MediaBrowser.Controller.SyncPlay
public class GroupMember
{
/// <summary>
- /// Gets or sets whether this member is buffering.
+ /// Gets or sets a value indicating whether this member is buffering.
/// </summary>
/// <value><c>true</c> if member is buffering; <c>false</c> otherwise.</value>
public bool IsBuffering { get; set; }
diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs
index de1fcd259..45c543806 100644
--- a/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs
+++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs
@@ -33,7 +33,7 @@ namespace MediaBrowser.Controller.SyncPlay
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The cancellation token.</param>
- void InitGroup(SessionInfo session, CancellationToken cancellationToken);
+ void CreateGroup(SessionInfo session, CancellationToken cancellationToken);
/// <summary>
/// Adds the session to the group.
diff --git a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs
index a6553563f..b036a2cc8 100644
--- a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs
+++ b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs
@@ -7,12 +7,43 @@ using MediaBrowser.Model.IO;
namespace MediaBrowser.LocalMetadata
{
+ /// <summary>
+ /// The BaseXmlProvider.
+ /// </summary>
+ /// <typeparam name="T">Type of provider.</typeparam>
public abstract class BaseXmlProvider<T> : ILocalMetadataProvider<T>, IHasItemChangeMonitor, IHasOrder
where T : BaseItem, new()
{
- protected IFileSystem FileSystem;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BaseXmlProvider{T}"/> class.
+ /// </summary>
+ /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+ protected BaseXmlProvider(IFileSystem fileSystem)
+ {
+ this.FileSystem = fileSystem;
+ }
+
+ /// <inheritdoc />
+ public string Name => XmlProviderUtils.Name;
- public Task<MetadataResult<T>> GetMetadata(ItemInfo info,
+ /// After Nfo
+ /// <inheritdoc />
+ public virtual int Order => 1;
+
+ /// <summary>
+ /// Gets the IFileSystem.
+ /// </summary>
+ protected IFileSystem FileSystem { get; }
+
+ /// <summary>
+ /// Gets metadata for item.
+ /// </summary>
+ /// <param name="info">The item info.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>The metadata for item.</returns>
+ public Task<MetadataResult<T>> GetMetadata(
+ ItemInfo info,
IDirectoryService directoryService,
CancellationToken cancellationToken)
{
@@ -46,15 +77,23 @@ namespace MediaBrowser.LocalMetadata
return Task.FromResult(result);
}
+ /// <summary>
+ /// Get metadata from path.
+ /// </summary>
+ /// <param name="result">Resulting metadata.</param>
+ /// <param name="path">The path.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
protected abstract void Fetch(MetadataResult<T> result, string path, CancellationToken cancellationToken);
- protected BaseXmlProvider(IFileSystem fileSystem)
- {
- FileSystem = fileSystem;
- }
-
+ /// <summary>
+ /// Get metadata from xml file.
+ /// </summary>
+ /// <param name="info">Item inf.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
+ /// <returns>The file system metadata.</returns>
protected abstract FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService);
+ /// <inheritdoc />
public bool HasChanged(BaseItem item, IDirectoryService directoryService)
{
var file = GetXmlFile(new ItemInfo(item), directoryService);
@@ -66,15 +105,5 @@ namespace MediaBrowser.LocalMetadata
return file.Exists && FileSystem.GetLastWriteTimeUtc(file) > item.DateLastSaved;
}
-
- public string Name => XmlProviderUtils.Name;
-
- //After Nfo
- public virtual int Order => 1;
- }
-
- static class XmlProviderUtils
- {
- public static string Name => "Emby Xml";
}
}
diff --git a/MediaBrowser.LocalMetadata/Images/CollectionFolderImageProvider.cs b/MediaBrowser.LocalMetadata/Images/CollectionFolderLocalImageProvider.cs
index 3bab1243c..556bb6a0e 100644
--- a/MediaBrowser.LocalMetadata/Images/CollectionFolderImageProvider.cs
+++ b/MediaBrowser.LocalMetadata/Images/CollectionFolderLocalImageProvider.cs
@@ -5,30 +5,41 @@ using MediaBrowser.Model.IO;
namespace MediaBrowser.LocalMetadata.Images
{
+ /// <summary>
+ /// Collection folder local image provider.
+ /// </summary>
public class CollectionFolderLocalImageProvider : ILocalImageProvider, IHasOrder
{
private readonly IFileSystem _fileSystem;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CollectionFolderLocalImageProvider"/> class.
+ /// </summary>
+ /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
public CollectionFolderLocalImageProvider(IFileSystem fileSystem)
{
_fileSystem = fileSystem;
}
+ /// <inheritdoc />
public string Name => "Collection Folder Images";
+ /// Run after LocalImageProvider
+ /// <inheritdoc />
+ public int Order => 1;
+
+ /// <inheritdoc />
public bool Supports(BaseItem item)
{
return item is CollectionFolder && item.SupportsLocalMetadata;
}
- // Run after LocalImageProvider
- public int Order => 1;
-
+ /// <inheritdoc />
public List<LocalImageInfo> GetImages(BaseItem item, IDirectoryService directoryService)
{
var collectionFolder = (CollectionFolder)item;
- return new LocalImageProvider(_fileSystem).GetImages(item, collectionFolder.PhysicalLocations, true, directoryService);
+ return new LocalImageProvider(_fileSystem).GetImages(item, collectionFolder.PhysicalLocations, directoryService);
}
}
}
diff --git a/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs
index 2f4cca5ff..393ad2efb 100644
--- a/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs
+++ b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs
@@ -10,24 +10,35 @@ using MediaBrowser.Model.IO;
namespace MediaBrowser.LocalMetadata.Images
{
- public class EpisodeLocalLocalImageProvider : ILocalImageProvider, IHasOrder
+ /// <summary>
+ /// Episode local image provider.
+ /// </summary>
+ public class EpisodeLocalImageProvider : ILocalImageProvider, IHasOrder
{
private readonly IFileSystem _fileSystem;
- public EpisodeLocalLocalImageProvider(IFileSystem fileSystem)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="EpisodeLocalImageProvider"/> class.
+ /// </summary>
+ /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+ public EpisodeLocalImageProvider(IFileSystem fileSystem)
{
_fileSystem = fileSystem;
}
+ /// <inheritdoc />
public string Name => "Local Images";
+ /// <inheritdoc />
public int Order => 0;
+ /// <inheritdoc />
public bool Supports(BaseItem item)
{
return item is Episode && item.SupportsLocalMetadata;
}
+ /// <inheritdoc />
public List<LocalImageInfo> GetImages(BaseItem item, IDirectoryService directoryService)
{
var parentPath = Path.GetDirectoryName(item.Path);
@@ -58,23 +69,15 @@ namespace MediaBrowser.LocalMetadata.Images
if (string.Equals(filenameWithoutExtension, currentNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
{
- list.Add(new LocalImageInfo
- {
- FileInfo = i,
- Type = ImageType.Primary
- });
+ list.Add(new LocalImageInfo { FileInfo = i, Type = ImageType.Primary });
}
-
else if (string.Equals(thumbName, currentNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
{
- list.Add(new LocalImageInfo
- {
- FileInfo = i,
- Type = ImageType.Primary
- });
+ list.Add(new LocalImageInfo { FileInfo = i, Type = ImageType.Primary });
}
}
}
+
return list;
}
}
diff --git a/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs
index 5137e4c68..509b5d700 100644
--- a/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs
+++ b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs
@@ -9,12 +9,21 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.LocalMetadata.Images
{
+ /// <summary>
+ /// Internal metadata folder image provider.
+ /// </summary>
public class InternalMetadataFolderImageProvider : ILocalImageProvider, IHasOrder
{
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private readonly ILogger<InternalMetadataFolderImageProvider> _logger;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="InternalMetadataFolderImageProvider"/> class.
+ /// </summary>
+ /// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
+ /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+ /// <param name="logger">Instance of the <see cref="ILogger{InternalMetadataFolderImageProvider}"/> interface.</param>
public InternalMetadataFolderImageProvider(
IServerConfigurationManager config,
IFileSystem fileSystem,
@@ -25,8 +34,14 @@ namespace MediaBrowser.LocalMetadata.Images
_logger = logger;
}
+ /// Make sure this is last so that all other locations are scanned first
+ /// <inheritdoc />
+ public int Order => 1000;
+
+ /// <inheritdoc />
public string Name => "Internal Images";
+ /// <inheritdoc />
public bool Supports(BaseItem item)
{
if (item is Photo)
@@ -52,9 +67,8 @@ namespace MediaBrowser.LocalMetadata.Images
return true;
}
- // Make sure this is last so that all other locations are scanned first
- public int Order => 1000;
+ /// <inheritdoc />
public List<LocalImageInfo> GetImages(BaseItem item, IDirectoryService directoryService)
{
var path = item.GetInternalMetadataPath();
@@ -66,7 +80,7 @@ namespace MediaBrowser.LocalMetadata.Images
try
{
- return new LocalImageProvider(_fileSystem).GetImages(item, path, false, directoryService);
+ return new LocalImageProvider(_fileSystem).GetImages(item, path, directoryService);
}
catch (IOException ex)
{
diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
index 16807f5a4..914db5305 100644
--- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
+++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
@@ -13,19 +13,71 @@ using MediaBrowser.Model.IO;
namespace MediaBrowser.LocalMetadata.Images
{
+ /// <summary>
+ /// Local image provider.
+ /// </summary>
public class LocalImageProvider : ILocalImageProvider, IHasOrder
{
+ private static readonly string[] _commonImageFileNames =
+ {
+ "poster",
+ "folder",
+ "cover",
+ "default"
+ };
+
+ private static readonly string[] _musicImageFileNames =
+ {
+ "folder",
+ "poster",
+ "cover",
+ "default"
+ };
+
+ private static readonly string[] _personImageFileNames =
+ {
+ "folder",
+ "poster"
+ };
+
+ private static readonly string[] _seriesImageFileNames =
+ {
+ "poster",
+ "folder",
+ "cover",
+ "default",
+ "show"
+ };
+
+ private static readonly string[] _videoImageFileNames =
+ {
+ "poster",
+ "folder",
+ "cover",
+ "default",
+ "movie"
+ };
+
private readonly IFileSystem _fileSystem;
+ private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="LocalImageProvider"/> class.
+ /// </summary>
+ /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
public LocalImageProvider(IFileSystem fileSystem)
{
_fileSystem = fileSystem;
}
+ /// <inheritdoc />
public string Name => "Local Images";
+ /// <inheritdoc />
public int Order => 0;
+ /// <inheritdoc />
public bool Supports(BaseItem item)
{
if (item.SupportsLocalMetadata)
@@ -42,15 +94,10 @@ namespace MediaBrowser.LocalMetadata.Images
if (item.LocationType == LocationType.Virtual)
{
var season = item as Season;
-
- if (season != null)
+ var series = season?.Series;
+ if (series != null && series.IsFileProtocol)
{
- var series = season.Series;
-
- if (series != null && series.IsFileProtocol)
- {
- return true;
- }
+ return true;
}
}
@@ -85,6 +132,7 @@ namespace MediaBrowser.LocalMetadata.Images
.OrderBy(i => Array.IndexOf(BaseItem.SupportedImageExtensions, i.Extension ?? string.Empty));
}
+ /// <inheritdoc />
public List<LocalImageInfo> GetImages(BaseItem item, IDirectoryService directoryService)
{
var files = GetFiles(item, true, directoryService).ToList();
@@ -96,12 +144,26 @@ namespace MediaBrowser.LocalMetadata.Images
return list;
}
- public List<LocalImageInfo> GetImages(BaseItem item, string path, bool isPathInMediaFolder, IDirectoryService directoryService)
+ /// <summary>
+ /// Get images for item.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="path">The images path.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
+ /// <returns>The local image info.</returns>
+ public List<LocalImageInfo> GetImages(BaseItem item, string path, IDirectoryService directoryService)
{
- return GetImages(item, new[] { path }, isPathInMediaFolder, directoryService);
+ return GetImages(item, new[] { path }, directoryService);
}
- public List<LocalImageInfo> GetImages(BaseItem item, IEnumerable<string> paths, bool arePathsInMediaFolders, IDirectoryService directoryService)
+ /// <summary>
+ /// Get images for item from multiple paths.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="paths">The image paths.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
+ /// <returns>The local image info.</returns>
+ public List<LocalImageInfo> GetImages(BaseItem item, IEnumerable<string> paths, IDirectoryService directoryService)
{
IEnumerable<FileSystemMetadata> files = paths.SelectMany(i => _fileSystem.GetFiles(i, BaseItem.SupportedImageExtensions, true, false));
@@ -196,7 +258,7 @@ namespace MediaBrowser.LocalMetadata.Images
if (!isEpisode && !isSong && !isPerson)
{
- PopulateBackdrops(item, images, files, imagePrefix, isInMixedFolder, directoryService);
+ PopulateBackdrops(item, images, files, imagePrefix, isInMixedFolder);
}
if (item is IHasScreenshots)
@@ -205,46 +267,6 @@ namespace MediaBrowser.LocalMetadata.Images
}
}
- private static readonly string[] CommonImageFileNames = new[]
- {
- "poster",
- "folder",
- "cover",
- "default"
- };
-
- private static readonly string[] MusicImageFileNames = new[]
- {
- "folder",
- "poster",
- "cover",
- "default"
- };
-
- private static readonly string[] PersonImageFileNames = new[]
- {
- "folder",
- "poster"
- };
-
- private static readonly string[] SeriesImageFileNames = new[]
- {
- "poster",
- "folder",
- "cover",
- "default",
- "show"
- };
-
- private static readonly string[] VideoImageFileNames = new[]
- {
- "poster",
- "folder",
- "cover",
- "default",
- "movie"
- };
-
private void PopulatePrimaryImages(BaseItem item, List<LocalImageInfo> images, List<FileSystemMetadata> files, string imagePrefix, bool isInMixedFolder)
{
string[] imageFileNames;
@@ -252,24 +274,24 @@ namespace MediaBrowser.LocalMetadata.Images
if (item is MusicAlbum || item is MusicArtist || item is PhotoAlbum)
{
// these prefer folder
- imageFileNames = MusicImageFileNames;
+ imageFileNames = _musicImageFileNames;
}
else if (item is Person)
{
// these prefer folder
- imageFileNames = PersonImageFileNames;
+ imageFileNames = _personImageFileNames;
}
else if (item is Series)
{
- imageFileNames = SeriesImageFileNames;
+ imageFileNames = _seriesImageFileNames;
}
else if (item is Video && !(item is Episode))
{
- imageFileNames = VideoImageFileNames;
+ imageFileNames = _videoImageFileNames;
}
else
{
- imageFileNames = CommonImageFileNames;
+ imageFileNames = _commonImageFileNames;
}
var fileNameWithoutExtension = item.FileNameWithoutExtension;
@@ -301,7 +323,7 @@ namespace MediaBrowser.LocalMetadata.Images
}
}
- private void PopulateBackdrops(BaseItem item, List<LocalImageInfo> images, List<FileSystemMetadata> files, string imagePrefix, bool isInMixedFolder, IDirectoryService directoryService)
+ private void PopulateBackdrops(BaseItem item, List<LocalImageInfo> images, List<FileSystemMetadata> files, string imagePrefix, bool isInMixedFolder)
{
if (!string.IsNullOrEmpty(item.Path))
{
@@ -328,13 +350,13 @@ namespace MediaBrowser.LocalMetadata.Images
if (extraFanartFolder != null)
{
- PopulateBackdropsFromExtraFanart(extraFanartFolder.FullName, images, directoryService);
+ PopulateBackdropsFromExtraFanart(extraFanartFolder.FullName, images);
}
PopulateBackdrops(images, files, imagePrefix, "backdrop", "backdrop", isInMixedFolder, ImageType.Backdrop);
}
- private void PopulateBackdropsFromExtraFanart(string path, List<LocalImageInfo> images, IDirectoryService directoryService)
+ private void PopulateBackdropsFromExtraFanart(string path, List<LocalImageInfo> images)
{
var imageFiles = _fileSystem.GetFiles(path, BaseItem.SupportedImageExtensions, false, false);
@@ -395,8 +417,6 @@ namespace MediaBrowser.LocalMetadata.Images
}
}
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
private void PopulateSeasonImagesFromSeriesFolder(Season season, List<LocalImageInfo> images, IDirectoryService directoryService)
{
var seasonNumber = season.IndexNumber;
@@ -410,7 +430,7 @@ namespace MediaBrowser.LocalMetadata.Images
var seriesFiles = GetFiles(series, false, directoryService).ToList();
// Try using the season name
- var prefix = season.Name.ToLowerInvariant().Replace(" ", string.Empty);
+ var prefix = season.Name.Replace(" ", string.Empty, StringComparison.Ordinal).ToLowerInvariant();
var filenamePrefixes = new List<string> { prefix };
diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
index 24104d779..529e7065c 100644
--- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
+++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
@@ -10,14 +10,28 @@
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
</ItemGroup>
- <ItemGroup>
- <Compile Include="..\SharedVersion.cs" />
- </ItemGroup>
-
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <Compile Include="..\SharedVersion.cs" />
+ </ItemGroup>
+
+ <!-- Code Analyzers-->
+ <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+ </ItemGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project>
diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs
index 4f1112396..4ac249840 100644
--- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs
+++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs
@@ -14,24 +14,21 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.LocalMetadata.Parsers
{
/// <summary>
- /// Provides a base class for parsing metadata xml
+ /// Provides a base class for parsing metadata xml.
/// </summary>
- /// <typeparam name="T"></typeparam>
+ /// <typeparam name="T">Type of item xml parser.</typeparam>
public class BaseItemXmlParser<T>
where T : BaseItem
{
- /// <summary>
- /// The logger
- /// </summary>
- protected ILogger<BaseItemXmlParser<T>> Logger { get; private set; }
- protected IProviderManager ProviderManager { get; private set; }
+ private readonly CultureInfo _usCulture = new CultureInfo("en-US");
- private Dictionary<string, string> _validProviderIds;
+ private Dictionary<string, string>? _validProviderIds;
/// <summary>
/// Initializes a new instance of the <see cref="BaseItemXmlParser{T}" /> class.
/// </summary>
- /// <param name="logger">The logger.</param>
+ /// <param name="logger">Instance of the <see cref="ILogger{BaseItemXmlParser}"/> interface.</param>
+ /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
public BaseItemXmlParser(ILogger<BaseItemXmlParser<T>> logger, IProviderManager providerManager)
{
Logger = logger;
@@ -39,12 +36,22 @@ namespace MediaBrowser.LocalMetadata.Parsers
}
/// <summary>
- /// Fetches metadata for an item from one xml file
+ /// Gets the logger.
+ /// </summary>
+ protected ILogger<BaseItemXmlParser<T>> Logger { get; private set; }
+
+ /// <summary>
+ /// Gets the provider manager.
+ /// </summary>
+ protected IProviderManager ProviderManager { get; private set; }
+
+ /// <summary>
+ /// Fetches metadata for an item from one xml file.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="metadataFile">The metadata file.</param>
/// <param name="cancellationToken">The cancellation token.</param>
- /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentNullException">Item is null.</exception>
public void Fetch(MetadataResult<T> item, string metadataFile, CancellationToken cancellationToken)
{
if (item == null)
@@ -57,7 +64,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
throw new ArgumentException("The metadata file was empty or null.", nameof(metadataFile));
}
- var settings = new XmlReaderSettings()
+ var settings = new XmlReaderSettings
{
ValidationType = ValidationType.None,
CheckCharacters = false,
@@ -74,14 +81,14 @@ namespace MediaBrowser.LocalMetadata.Parsers
var id = info.Key + "Id";
if (!_validProviderIds.ContainsKey(id))
{
- _validProviderIds.Add(id, info.Key);
+ _validProviderIds.Add(id, info.Key!);
}
}
- //Additional Mappings
+ // Additional Mappings
_validProviderIds.Add("IMDB", "Imdb");
- //Fetch(item, metadataFile, settings, Encoding.GetEncoding("ISO-8859-1"), cancellationToken);
+ // Fetch(item, metadataFile, settings, Encoding.GetEncoding("ISO-8859-1"), cancellationToken);
Fetch(item, metadataFile, settings, Encoding.UTF8, cancellationToken);
}
@@ -97,34 +104,30 @@ namespace MediaBrowser.LocalMetadata.Parsers
{
item.ResetPeople();
- using (var fileStream = File.OpenRead(metadataFile))
- using (var streamReader = new StreamReader(fileStream, encoding))
- using (var reader = XmlReader.Create(streamReader, settings))
+ using var fileStream = File.OpenRead(metadataFile);
+ using var streamReader = new StreamReader(fileStream, encoding);
+ using var reader = XmlReader.Create(streamReader, settings);
+ reader.MoveToContent();
+ reader.Read();
+
+ // Loop through each element
+ while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{
- reader.MoveToContent();
- reader.Read();
+ cancellationToken.ThrowIfCancellationRequested();
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ if (reader.NodeType == XmlNodeType.Element)
{
- cancellationToken.ThrowIfCancellationRequested();
-
- if (reader.NodeType == XmlNodeType.Element)
- {
- FetchDataFromXmlNode(reader, item);
- }
- else
- {
- reader.Read();
- }
+ FetchDataFromXmlNode(reader, item);
+ }
+ else
+ {
+ reader.Read();
}
}
}
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
/// <summary>
- /// Fetches metadata from one Xml Element
+ /// Fetches metadata from one Xml Element.
/// </summary>
/// <param name="reader">The reader.</param>
/// <param name="itemResult">The item result.</param>
@@ -136,554 +139,568 @@ namespace MediaBrowser.LocalMetadata.Parsers
{
// DateCreated
case "Added":
- {
- var val = reader.ReadElementContentAsString();
+ {
+ var val = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(val))
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ if (DateTime.TryParse(val, out var added))
{
- if (DateTime.TryParse(val, out var added))
- {
- item.DateCreated = added.ToUniversalTime();
- }
- else
- {
- Logger.LogWarning("Invalid Added value found: " + val);
- }
+ item.DateCreated = added.ToUniversalTime();
+ }
+ else
+ {
+ Logger.LogWarning("Invalid Added value found: " + val);
}
- break;
}
+ break;
+ }
+
case "OriginalTitle":
- {
- var val = reader.ReadElementContentAsString();
+ {
+ var val = reader.ReadElementContentAsString();
- if (!string.IsNullOrEmpty(val))
- {
- item.OriginalTitle = val;
- }
- break;
+ if (!string.IsNullOrEmpty(val))
+ {
+ item.OriginalTitle = val;
}
+ break;
+ }
+
case "LocalTitle":
item.Name = reader.ReadElementContentAsString();
break;
case "CriticRating":
- {
- var text = reader.ReadElementContentAsString();
+ {
+ var text = reader.ReadElementContentAsString();
- if (!string.IsNullOrEmpty(text))
+ if (!string.IsNullOrEmpty(text))
+ {
+ if (float.TryParse(text, NumberStyles.Any, _usCulture, out var value))
{
- if (float.TryParse(text, NumberStyles.Any, _usCulture, out var value))
- {
- item.CriticRating = value;
- }
+ item.CriticRating = value;
}
-
- break;
}
+ break;
+ }
+
case "SortTitle":
- {
- var val = reader.ReadElementContentAsString();
+ {
+ var val = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(val))
- {
- item.ForcedSortName = val;
- }
- break;
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ item.ForcedSortName = val;
}
+ break;
+ }
+
case "Overview":
case "Description":
- {
- var val = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- item.Overview = val;
- }
+ {
+ var val = reader.ReadElementContentAsString();
- break;
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ item.Overview = val;
}
+ break;
+ }
+
case "Language":
- {
- var val = reader.ReadElementContentAsString();
+ {
+ var val = reader.ReadElementContentAsString();
- item.PreferredMetadataLanguage = val;
+ item.PreferredMetadataLanguage = val;
- break;
- }
+ break;
+ }
case "CountryCode":
- {
- var val = reader.ReadElementContentAsString();
+ {
+ var val = reader.ReadElementContentAsString();
- item.PreferredMetadataCountryCode = val;
+ item.PreferredMetadataCountryCode = val;
- break;
- }
+ break;
+ }
case "PlaceOfBirth":
- {
- var val = reader.ReadElementContentAsString();
+ {
+ var val = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(val))
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ if (item is Person person)
{
- var person = item as Person;
- if (person != null)
- {
- person.ProductionLocations = new string[] { val };
- }
+ person.ProductionLocations = new[] { val };
}
-
- break;
}
+ break;
+ }
+
case "LockedFields":
- {
- var val = reader.ReadElementContentAsString();
+ {
+ var val = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(val))
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ item.LockedFields = val.Split('|').Select(i =>
{
- item.LockedFields = val.Split('|').Select(i =>
+ if (Enum.TryParse(i, true, out MetadataField field))
{
- if (Enum.TryParse(i, true, out MetadataField field))
- {
- return (MetadataField?)field;
- }
-
- return null;
-
- }).Where(i => i.HasValue).Select(i => i.Value).ToArray();
- }
+ return (MetadataField?)field;
+ }
- break;
+ return null;
+ }).Where(i => i.HasValue).Select(i => i!.Value).ToArray();
}
+ break;
+ }
+
case "TagLines":
+ {
+ if (!reader.IsEmptyElement)
{
- if (!reader.IsEmptyElement)
- {
- using (var subtree = reader.ReadSubtree())
- {
- FetchFromTaglinesNode(subtree, item);
- }
- }
- else
+ using (var subtree = reader.ReadSubtree())
{
- reader.Read();
+ FetchFromTaglinesNode(subtree, item);
}
- break;
+ }
+ else
+ {
+ reader.Read();
}
+ break;
+ }
+
case "Countries":
+ {
+ if (!reader.IsEmptyElement)
{
- if (!reader.IsEmptyElement)
+ using (var subtree = reader.ReadSubtree())
{
- using (var subtree = reader.ReadSubtree())
- {
- FetchFromCountriesNode(subtree, item);
- }
+ FetchFromCountriesNode(subtree);
}
- else
- {
- reader.Read();
- }
- break;
}
+ else
+ {
+ reader.Read();
+ }
+
+ break;
+ }
case "ContentRating":
case "MPAARating":
- {
- var rating = reader.ReadElementContentAsString();
+ {
+ var rating = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(rating))
- {
- item.OfficialRating = rating;
- }
- break;
+ if (!string.IsNullOrWhiteSpace(rating))
+ {
+ item.OfficialRating = rating;
}
+ break;
+ }
+
case "CustomRating":
- {
- var val = reader.ReadElementContentAsString();
+ {
+ var val = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(val))
- {
- item.CustomRating = val;
- }
- break;
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ item.CustomRating = val;
}
+ break;
+ }
+
case "RunningTime":
- {
- var text = reader.ReadElementContentAsString();
+ {
+ var text = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(text))
+ if (!string.IsNullOrWhiteSpace(text))
+ {
+ if (int.TryParse(text.Split(' ')[0], NumberStyles.Integer, _usCulture, out var runtime))
{
- if (int.TryParse(text.Split(' ')[0], NumberStyles.Integer, _usCulture, out var runtime))
- {
- item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks;
- }
+ item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks;
}
- break;
}
+ break;
+ }
+
case "AspectRatio":
- {
- var val = reader.ReadElementContentAsString();
+ {
+ var val = reader.ReadElementContentAsString();
- var hasAspectRatio = item as IHasAspectRatio;
- if (!string.IsNullOrWhiteSpace(val) && hasAspectRatio != null)
- {
- hasAspectRatio.AspectRatio = val;
- }
- break;
+ var hasAspectRatio = item as IHasAspectRatio;
+ if (!string.IsNullOrWhiteSpace(val) && hasAspectRatio != null)
+ {
+ hasAspectRatio.AspectRatio = val;
}
+ break;
+ }
+
case "LockData":
- {
- var val = reader.ReadElementContentAsString();
+ {
+ var val = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(val))
- {
- item.IsLocked = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- break;
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ item.IsLocked = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
}
+ break;
+ }
+
case "Network":
+ {
+ foreach (var name in SplitNames(reader.ReadElementContentAsString()))
{
- foreach (var name in SplitNames(reader.ReadElementContentAsString()))
+ if (string.IsNullOrWhiteSpace(name))
{
- if (string.IsNullOrWhiteSpace(name))
- {
- continue;
- }
- item.AddStudio(name);
+ continue;
}
- break;
+
+ item.AddStudio(name);
}
+ break;
+ }
+
case "Director":
+ {
+ foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.Director }))
{
- foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.Director }))
+ if (string.IsNullOrWhiteSpace(p.Name))
{
- if (string.IsNullOrWhiteSpace(p.Name))
- {
- continue;
- }
- itemResult.AddPerson(p);
+ continue;
}
- break;
+
+ itemResult.AddPerson(p);
}
+
+ break;
+ }
+
case "Writer":
+ {
+ foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.Writer }))
{
- foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.Writer }))
+ if (string.IsNullOrWhiteSpace(p.Name))
{
- if (string.IsNullOrWhiteSpace(p.Name))
- {
- continue;
- }
- itemResult.AddPerson(p);
+ continue;
}
- break;
+
+ itemResult.AddPerson(p);
}
- case "Actors":
- {
+ break;
+ }
- var actors = reader.ReadInnerXml();
+ case "Actors":
+ {
+ var actors = reader.ReadInnerXml();
- if (actors.Contains("<"))
- {
- // This is one of the mis-named "Actors" full nodes created by MB2
- // Create a reader and pass it to the persons node processor
- FetchDataFromPersonsNode(XmlReader.Create(new StringReader("<Persons>" + actors + "</Persons>")), itemResult);
- }
- else
- {
- // Old-style piped string
- foreach (var p in SplitNames(actors).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.Actor }))
- {
- if (string.IsNullOrWhiteSpace(p.Name))
- {
- continue;
- }
- itemResult.AddPerson(p);
- }
- }
- break;
+ if (actors.Contains("<", StringComparison.Ordinal))
+ {
+ // This is one of the mis-named "Actors" full nodes created by MB2
+ // Create a reader and pass it to the persons node processor
+ using var xmlReader = XmlReader.Create(new StringReader($"<Persons>{actors}</Persons>"));
+ FetchDataFromPersonsNode(xmlReader, itemResult);
}
-
- case "GuestStars":
+ else
{
- foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.GuestStar }))
+ // Old-style piped string
+ foreach (var p in SplitNames(actors).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.Actor }))
{
if (string.IsNullOrWhiteSpace(p.Name))
{
continue;
}
+
itemResult.AddPerson(p);
}
- break;
}
- case "Trailer":
- {
- var val = reader.ReadElementContentAsString();
+ break;
+ }
- if (!string.IsNullOrWhiteSpace(val))
+ case "GuestStars":
+ {
+ foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.GuestStar }))
+ {
+ if (string.IsNullOrWhiteSpace(p.Name))
{
- item.AddTrailerUrl(val);
+ continue;
}
- break;
+
+ itemResult.AddPerson(p);
}
- case "DisplayOrder":
+ break;
+ }
+
+ case "Trailer":
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val))
{
- var val = reader.ReadElementContentAsString();
+ item.AddTrailerUrl(val);
+ }
+
+ break;
+ }
+
+ case "DisplayOrder":
+ {
+ var val = reader.ReadElementContentAsString();
- var hasDisplayOrder = item as IHasDisplayOrder;
- if (hasDisplayOrder != null)
+ var hasDisplayOrder = item as IHasDisplayOrder;
+ if (hasDisplayOrder != null)
+ {
+ if (!string.IsNullOrWhiteSpace(val))
{
- if (!string.IsNullOrWhiteSpace(val))
- {
- hasDisplayOrder.DisplayOrder = val;
- }
+ hasDisplayOrder.DisplayOrder = val;
}
- break;
}
+ break;
+ }
+
case "Trailers":
+ {
+ if (!reader.IsEmptyElement)
{
- if (!reader.IsEmptyElement)
- {
- using (var subtree = reader.ReadSubtree())
- {
- FetchDataFromTrailersNode(subtree, item);
- }
- }
- else
- {
- reader.Read();
- }
- break;
+ using var subtree = reader.ReadSubtree();
+ FetchDataFromTrailersNode(subtree, item);
+ }
+ else
+ {
+ reader.Read();
}
+ break;
+ }
+
case "ProductionYear":
- {
- var val = reader.ReadElementContentAsString();
+ {
+ var val = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(val))
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ if (int.TryParse(val, out var productionYear) && productionYear > 1850)
{
- if (int.TryParse(val, out var productionYear) && productionYear > 1850)
- {
- item.ProductionYear = productionYear;
- }
+ item.ProductionYear = productionYear;
}
-
- break;
}
+ break;
+ }
+
case "Rating":
case "IMDBrating":
- {
-
- var rating = reader.ReadElementContentAsString();
+ {
+ var rating = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(rating))
+ if (!string.IsNullOrWhiteSpace(rating))
+ {
+ // All external meta is saving this as '.' for decimal I believe...but just to be sure
+ if (float.TryParse(rating.Replace(',', '.'), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var val))
{
- // All external meta is saving this as '.' for decimal I believe...but just to be sure
- if (float.TryParse(rating.Replace(',', '.'), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var val))
- {
- item.CommunityRating = val;
- }
+ item.CommunityRating = val;
}
- break;
}
+ break;
+ }
+
case "BirthDate":
case "PremiereDate":
case "FirstAired":
- {
- var firstAired = reader.ReadElementContentAsString();
+ {
+ var firstAired = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(firstAired))
+ if (!string.IsNullOrWhiteSpace(firstAired))
+ {
+ if (DateTime.TryParseExact(firstAired, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var airDate) && airDate.Year > 1850)
{
- if (DateTime.TryParseExact(firstAired, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var airDate) && airDate.Year > 1850)
- {
- item.PremiereDate = airDate.ToUniversalTime();
- item.ProductionYear = airDate.Year;
- }
+ item.PremiereDate = airDate.ToUniversalTime();
+ item.ProductionYear = airDate.Year;
}
-
- break;
}
+ break;
+ }
+
case "DeathDate":
case "EndDate":
- {
- var firstAired = reader.ReadElementContentAsString();
+ {
+ var firstAired = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(firstAired))
+ if (!string.IsNullOrWhiteSpace(firstAired))
+ {
+ if (DateTime.TryParseExact(firstAired, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var airDate) && airDate.Year > 1850)
{
- if (DateTime.TryParseExact(firstAired, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var airDate) && airDate.Year > 1850)
- {
- item.EndDate = airDate.ToUniversalTime();
- }
+ item.EndDate = airDate.ToUniversalTime();
}
-
- break;
}
+ break;
+ }
+
case "CollectionNumber":
var tmdbCollection = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(tmdbCollection))
{
item.SetProviderId(MetadataProvider.TmdbCollection, tmdbCollection);
}
+
break;
case "Genres":
+ {
+ if (!reader.IsEmptyElement)
{
- if (!reader.IsEmptyElement)
- {
- using (var subtree = reader.ReadSubtree())
- {
- FetchFromGenresNode(subtree, item);
- }
- }
- else
- {
- reader.Read();
- }
- break;
+ using var subtree = reader.ReadSubtree();
+ FetchFromGenresNode(subtree, item);
+ }
+ else
+ {
+ reader.Read();
}
+ break;
+ }
+
case "Tags":
+ {
+ if (!reader.IsEmptyElement)
{
- if (!reader.IsEmptyElement)
- {
- using (var subtree = reader.ReadSubtree())
- {
- FetchFromTagsNode(subtree, item);
- }
- }
- else
- {
- reader.Read();
- }
- break;
+ using var subtree = reader.ReadSubtree();
+ FetchFromTagsNode(subtree, item);
+ }
+ else
+ {
+ reader.Read();
}
+ break;
+ }
+
case "Persons":
+ {
+ if (!reader.IsEmptyElement)
{
- if (!reader.IsEmptyElement)
- {
- using (var subtree = reader.ReadSubtree())
- {
- FetchDataFromPersonsNode(subtree, itemResult);
- }
- }
- else
- {
- reader.Read();
- }
- break;
+ using var subtree = reader.ReadSubtree();
+ FetchDataFromPersonsNode(subtree, itemResult);
+ }
+ else
+ {
+ reader.Read();
}
+ break;
+ }
+
case "Studios":
+ {
+ if (!reader.IsEmptyElement)
{
- if (!reader.IsEmptyElement)
- {
- using (var subtree = reader.ReadSubtree())
- {
- FetchFromStudiosNode(subtree, item);
- }
- }
- else
- {
- reader.Read();
- }
- break;
+ using var subtree = reader.ReadSubtree();
+ FetchFromStudiosNode(subtree, item);
+ }
+ else
+ {
+ reader.Read();
}
+ break;
+ }
+
case "Shares":
+ {
+ if (!reader.IsEmptyElement)
{
- if (!reader.IsEmptyElement)
+ using var subtree = reader.ReadSubtree();
+ if (item is IHasShares hasShares)
{
- using (var subtree = reader.ReadSubtree())
- {
- var hasShares = item as IHasShares;
- if (hasShares != null)
- {
- FetchFromSharesNode(subtree, hasShares);
- }
- }
- }
- else
- {
- reader.Read();
+ FetchFromSharesNode(subtree, hasShares);
}
- break;
}
-
- case "Format3D":
+ else
{
- var val = reader.ReadElementContentAsString();
+ reader.Read();
+ }
- var video = item as Video;
+ break;
+ }
- if (video != null)
- {
- if (string.Equals("HSBS", val, StringComparison.OrdinalIgnoreCase))
- {
- video.Video3DFormat = Video3DFormat.HalfSideBySide;
- }
- else if (string.Equals("HTAB", val, StringComparison.OrdinalIgnoreCase))
- {
- video.Video3DFormat = Video3DFormat.HalfTopAndBottom;
- }
- else if (string.Equals("FTAB", val, StringComparison.OrdinalIgnoreCase))
- {
- video.Video3DFormat = Video3DFormat.FullTopAndBottom;
- }
- else if (string.Equals("FSBS", val, StringComparison.OrdinalIgnoreCase))
- {
- video.Video3DFormat = Video3DFormat.FullSideBySide;
- }
- else if (string.Equals("MVC", val, StringComparison.OrdinalIgnoreCase))
- {
- video.Video3DFormat = Video3DFormat.MVC;
- }
- }
- break;
- }
+ case "Format3D":
+ {
+ var val = reader.ReadElementContentAsString();
- default:
+ if (item is Video video)
{
- string readerName = reader.Name;
- if (_validProviderIds.TryGetValue(readerName, out string providerIdValue))
+ if (string.Equals("HSBS", val, StringComparison.OrdinalIgnoreCase))
{
- var id = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(id))
- {
- item.SetProviderId(providerIdValue, id);
- }
+ video.Video3DFormat = Video3DFormat.HalfSideBySide;
}
- else
+ else if (string.Equals("HTAB", val, StringComparison.OrdinalIgnoreCase))
{
- reader.Skip();
+ video.Video3DFormat = Video3DFormat.HalfTopAndBottom;
+ }
+ else if (string.Equals("FTAB", val, StringComparison.OrdinalIgnoreCase))
+ {
+ video.Video3DFormat = Video3DFormat.FullTopAndBottom;
+ }
+ else if (string.Equals("FSBS", val, StringComparison.OrdinalIgnoreCase))
+ {
+ video.Video3DFormat = Video3DFormat.FullSideBySide;
+ }
+ else if (string.Equals("MVC", val, StringComparison.OrdinalIgnoreCase))
+ {
+ video.Video3DFormat = Video3DFormat.MVC;
}
+ }
- break;
+ break;
+ }
+ default:
+ {
+ string readerName = reader.Name;
+ if (_validProviderIds!.TryGetValue(readerName, out string providerIdValue))
+ {
+ var id = reader.ReadElementContentAsString();
+ if (!string.IsNullOrWhiteSpace(id))
+ {
+ item.SetProviderId(providerIdValue, id);
+ }
}
+ else
+ {
+ reader.Skip();
+ }
+
+ break;
+ }
}
}
+
private void FetchFromSharesNode(XmlReader reader, IHasShares item)
{
var list = new List<Share>();
@@ -699,30 +716,31 @@ namespace MediaBrowser.LocalMetadata.Parsers
switch (reader.Name)
{
case "Share":
+ {
+ if (reader.IsEmptyElement)
{
- if (reader.IsEmptyElement)
- {
- reader.Read();
- continue;
- }
+ reader.Read();
+ continue;
+ }
- using (var subReader = reader.ReadSubtree())
- {
- var child = GetShare(subReader);
+ using (var subReader = reader.ReadSubtree())
+ {
+ var child = GetShare(subReader);
- if (child != null)
- {
- list.Add(child);
- }
+ if (child != null)
+ {
+ list.Add(child);
}
-
- break;
}
+
+ break;
+ }
+
default:
- {
- reader.Skip();
- break;
- }
+ {
+ reader.Skip();
+ break;
+ }
}
}
else
@@ -749,16 +767,16 @@ namespace MediaBrowser.LocalMetadata.Parsers
switch (reader.Name)
{
case "UserId":
- {
- share.UserId = reader.ReadElementContentAsString();
- break;
- }
+ {
+ share.UserId = reader.ReadElementContentAsString();
+ break;
+ }
case "CanEdit":
- {
- share.CanEdit = string.Equals(reader.ReadElementContentAsString(), true.ToString(), StringComparison.OrdinalIgnoreCase);
- break;
- }
+ {
+ share.CanEdit = string.Equals(reader.ReadElementContentAsString(), true.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase);
+ break;
+ }
default:
reader.Skip();
@@ -774,7 +792,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
return share;
}
- private void FetchFromCountriesNode(XmlReader reader, T item)
+ private void FetchFromCountriesNode(XmlReader reader)
{
reader.MoveToContent();
reader.Read();
@@ -787,15 +805,16 @@ namespace MediaBrowser.LocalMetadata.Parsers
switch (reader.Name)
{
case "Country":
- {
- var val = reader.ReadElementContentAsString();
+ {
+ var val = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(val))
- {
- }
- break;
+ if (!string.IsNullOrWhiteSpace(val))
+ {
}
+ break;
+ }
+
default:
reader.Skip();
break;
@@ -826,15 +845,17 @@ namespace MediaBrowser.LocalMetadata.Parsers
switch (reader.Name)
{
case "Tagline":
- {
- var val = reader.ReadElementContentAsString();
+ {
+ var val = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(val))
- {
- item.Tagline = val;
- }
- break;
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ item.Tagline = val;
}
+
+ break;
+ }
+
default:
reader.Skip();
break;
@@ -865,16 +886,17 @@ namespace MediaBrowser.LocalMetadata.Parsers
switch (reader.Name)
{
case "Genre":
- {
- var genre = reader.ReadElementContentAsString();
+ {
+ var genre = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(genre))
- {
- item.AddGenre(genre);
- }
- break;
+ if (!string.IsNullOrWhiteSpace(genre))
+ {
+ item.AddGenre(genre);
}
+ break;
+ }
+
default:
reader.Skip();
break;
@@ -902,16 +924,17 @@ namespace MediaBrowser.LocalMetadata.Parsers
switch (reader.Name)
{
case "Tag":
- {
- var tag = reader.ReadElementContentAsString();
+ {
+ var tag = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(tag))
- {
- tags.Add(tag);
- }
- break;
+ if (!string.IsNullOrWhiteSpace(tag))
+ {
+ tags.Add(tag);
}
+ break;
+ }
+
default:
reader.Skip();
break;
@@ -945,26 +968,29 @@ namespace MediaBrowser.LocalMetadata.Parsers
{
case "Person":
case "Actor":
+ {
+ if (reader.IsEmptyElement)
{
- if (reader.IsEmptyElement)
- {
- reader.Read();
- continue;
- }
- using (var subtree = reader.ReadSubtree())
+ reader.Read();
+ continue;
+ }
+
+ using (var subtree = reader.ReadSubtree())
+ {
+ foreach (var person in GetPersonsFromXmlNode(subtree))
{
- foreach (var person in GetPersonsFromXmlNode(subtree))
+ if (string.IsNullOrWhiteSpace(person.Name))
{
- if (string.IsNullOrWhiteSpace(person.Name))
- {
- continue;
- }
- item.AddPerson(person);
+ continue;
}
+
+ item.AddPerson(person);
}
- break;
}
+ break;
+ }
+
default:
reader.Skip();
break;
@@ -990,16 +1016,17 @@ namespace MediaBrowser.LocalMetadata.Parsers
switch (reader.Name)
{
case "Trailer":
- {
- var val = reader.ReadElementContentAsString();
+ {
+ var val = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(val))
- {
- item.AddTrailerUrl(val);
- }
- break;
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ item.AddTrailerUrl(val);
}
+ break;
+ }
+
default:
reader.Skip();
break;
@@ -1030,16 +1057,17 @@ namespace MediaBrowser.LocalMetadata.Parsers
switch (reader.Name)
{
case "Studio":
- {
- var studio = reader.ReadElementContentAsString();
+ {
+ var studio = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(studio))
- {
- item.AddStudio(studio);
- }
- break;
+ if (!string.IsNullOrWhiteSpace(studio))
+ {
+ item.AddStudio(studio);
}
+ break;
+ }
+
default:
reader.Skip();
break;
@@ -1060,7 +1088,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
private IEnumerable<PersonInfo> GetPersonsFromXmlNode(XmlReader reader)
{
var name = string.Empty;
- var type = PersonType.Actor; // If type is not specified assume actor
+ var type = PersonType.Actor; // If type is not specified assume actor
var role = string.Empty;
int? sortOrder = null;
@@ -1079,40 +1107,44 @@ namespace MediaBrowser.LocalMetadata.Parsers
break;
case "Type":
- {
- var val = reader.ReadElementContentAsString();
+ {
+ var val = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(val))
- {
- type = val;
- }
- break;
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ type = val;
}
+ break;
+ }
+
case "Role":
- {
- var val = reader.ReadElementContentAsString();
+ {
+ var val = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(val))
- {
- role = val;
- }
- break;
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ role = val;
}
+
+ break;
+ }
+
case "SortOrder":
- {
- var val = reader.ReadElementContentAsString();
+ {
+ var val = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(val))
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var intVal))
{
- if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var intVal))
- {
- sortOrder = intVal;
- }
+ sortOrder = intVal;
}
- break;
}
+ break;
+ }
+
default:
reader.Skip();
break;
@@ -1124,23 +1156,19 @@ namespace MediaBrowser.LocalMetadata.Parsers
}
}
- var personInfo = new PersonInfo
- {
- Name = name.Trim(),
- Role = role,
- Type = type,
- SortOrder = sortOrder
- };
+ var personInfo = new PersonInfo { Name = name.Trim(), Role = role, Type = type, SortOrder = sortOrder };
return new[] { personInfo };
}
- protected LinkedChild GetLinkedChild(XmlReader reader)
+ /// <summary>
+ /// Get linked child.
+ /// </summary>
+ /// <param name="reader">The xml reader.</param>
+ /// <returns>The linked child.</returns>
+ protected LinkedChild? GetLinkedChild(XmlReader reader)
{
- var linkedItem = new LinkedChild
- {
- Type = LinkedChildType.Manual
- };
+ var linkedItem = new LinkedChild { Type = LinkedChildType.Manual };
reader.MoveToContent();
reader.Read();
@@ -1153,15 +1181,16 @@ namespace MediaBrowser.LocalMetadata.Parsers
switch (reader.Name)
{
case "Path":
- {
- linkedItem.Path = reader.ReadElementContentAsString();
- break;
- }
+ {
+ linkedItem.Path = reader.ReadElementContentAsString();
+ break;
+ }
+
case "ItemId":
- {
- linkedItem.LibraryItemId = reader.ReadElementContentAsString();
- break;
- }
+ {
+ linkedItem.LibraryItemId = reader.ReadElementContentAsString();
+ break;
+ }
default:
reader.Skip();
@@ -1183,7 +1212,12 @@ namespace MediaBrowser.LocalMetadata.Parsers
return null;
}
- protected Share GetShare(XmlReader reader)
+ /// <summary>
+ /// Get share.
+ /// </summary>
+ /// <param name="reader">The xml reader.</param>
+ /// <returns>The share.</returns>
+ protected Share? GetShare(XmlReader reader)
{
var item = new Share();
@@ -1198,21 +1232,22 @@ namespace MediaBrowser.LocalMetadata.Parsers
switch (reader.Name)
{
case "UserId":
- {
- item.UserId = reader.ReadElementContentAsString();
- break;
- }
+ {
+ item.UserId = reader.ReadElementContentAsString();
+ break;
+ }
case "CanEdit":
- {
- item.CanEdit = string.Equals(reader.ReadElementContentAsString(), "true", StringComparison.OrdinalIgnoreCase);
- break;
- }
+ {
+ item.CanEdit = string.Equals(reader.ReadElementContentAsString(), "true", StringComparison.OrdinalIgnoreCase);
+ break;
+ }
+
default:
- {
- reader.Skip();
- break;
- }
+ {
+ reader.Skip();
+ break;
+ }
}
}
else
@@ -1230,19 +1265,19 @@ namespace MediaBrowser.LocalMetadata.Parsers
return null;
}
-
/// <summary>
- /// Used to split names of comma or pipe delimeted genres and people
+ /// Used to split names of comma or pipe delimited genres and people.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>IEnumerable{System.String}.</returns>
private IEnumerable<string> SplitNames(string value)
{
- value = value ?? string.Empty;
+ value ??= string.Empty;
// Only split by comma if there is no pipe in the string
// We have to be careful to not split names like Matthew, Jr.
- var separator = value.IndexOf('|') == -1 && value.IndexOf(';') == -1 ? new[] { ',' } : new[] { '|', ';' };
+ var separator = value.IndexOf('|', StringComparison.Ordinal) == -1
+ && value.IndexOf(';', StringComparison.Ordinal) == -1 ? new[] { ',' } : new[] { '|', ';' };
value = value.Trim().Trim(separator);
@@ -1250,7 +1285,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
}
/// <summary>
- /// Provides an additional overload for string.split
+ /// Provides an additional overload for string.split.
/// </summary>
/// <param name="val">The val.</param>
/// <param name="separators">The separators.</param>
@@ -1260,6 +1295,5 @@ namespace MediaBrowser.LocalMetadata.Parsers
{
return val.Split(separators, options);
}
-
}
}
diff --git a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs
index ca11a079d..ff846830b 100644
--- a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs
+++ b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs
@@ -7,8 +7,22 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.LocalMetadata.Parsers
{
+ /// <summary>
+ /// The box set xml parser.
+ /// </summary>
public class BoxSetXmlParser : BaseItemXmlParser<BoxSet>
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BoxSetXmlParser"/> class.
+ /// </summary>
+ /// <param name="logger">Instance of the <see cref="ILogger{BoxSetXmlParset}"/> interface.</param>
+ /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
+ public BoxSetXmlParser(ILogger<BoxSetXmlParser> logger, IProviderManager providerManager)
+ : base(logger, providerManager)
+ {
+ }
+
+ /// <inheritdoc />
protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult<BoxSet> item)
{
switch (reader.Name)
@@ -26,6 +40,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
{
reader.Read();
}
+
break;
default:
@@ -49,31 +64,32 @@ namespace MediaBrowser.LocalMetadata.Parsers
switch (reader.Name)
{
case "CollectionItem":
+ {
+ if (!reader.IsEmptyElement)
{
- if (!reader.IsEmptyElement)
+ using (var subReader = reader.ReadSubtree())
{
- using (var subReader = reader.ReadSubtree())
- {
- var child = GetLinkedChild(subReader);
+ var child = GetLinkedChild(subReader);
- if (child != null)
- {
- list.Add(child);
- }
+ if (child != null)
+ {
+ list.Add(child);
}
}
- else
- {
- reader.Read();
- }
-
- break;
}
- default:
+ else
{
- reader.Skip();
- break;
+ reader.Read();
}
+
+ break;
+ }
+
+ default:
+ {
+ reader.Skip();
+ break;
+ }
}
}
else
@@ -84,10 +100,5 @@ namespace MediaBrowser.LocalMetadata.Parsers
item.Item.LinkedChildren = list.ToArray();
}
-
- public BoxSetXmlParser(ILogger<BoxSetXmlParser> logger, IProviderManager providerManager)
- : base(logger, providerManager)
- {
- }
}
}
diff --git a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs
index 54710cd82..78c0fa8ad 100644
--- a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs
+++ b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs
@@ -7,8 +7,22 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.LocalMetadata.Parsers
{
+ /// <summary>
+ /// Playlist xml parser.
+ /// </summary>
public class PlaylistXmlParser : BaseItemXmlParser<Playlist>
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PlaylistXmlParser"/> class.
+ /// </summary>
+ /// <param name="logger">Instance of the <see cref="ILogger{PlaylistXmlParser}"/> interface.</param>
+ /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
+ public PlaylistXmlParser(ILogger<PlaylistXmlParser> logger, IProviderManager providerManager)
+ : base(logger, providerManager)
+ {
+ }
+
+ /// <inheritdoc />
protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult<Playlist> result)
{
var item = result.Item;
@@ -16,11 +30,11 @@ namespace MediaBrowser.LocalMetadata.Parsers
switch (reader.Name)
{
case "PlaylistMediaType":
- {
- item.PlaylistMediaType = reader.ReadElementContentAsString();
+ {
+ item.PlaylistMediaType = reader.ReadElementContentAsString();
- break;
- }
+ break;
+ }
case "PlaylistItems":
@@ -35,6 +49,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
{
reader.Read();
}
+
break;
default:
@@ -58,30 +73,31 @@ namespace MediaBrowser.LocalMetadata.Parsers
switch (reader.Name)
{
case "PlaylistItem":
+ {
+ if (reader.IsEmptyElement)
{
- if (reader.IsEmptyElement)
- {
- reader.Read();
- continue;
- }
+ reader.Read();
+ continue;
+ }
- using (var subReader = reader.ReadSubtree())
- {
- var child = GetLinkedChild(subReader);
+ using (var subReader = reader.ReadSubtree())
+ {
+ var child = GetLinkedChild(subReader);
- if (child != null)
- {
- list.Add(child);
- }
+ if (child != null)
+ {
+ list.Add(child);
}
-
- break;
}
+
+ break;
+ }
+
default:
- {
- reader.Skip();
- break;
- }
+ {
+ reader.Skip();
+ break;
+ }
}
}
else
@@ -92,10 +108,5 @@ namespace MediaBrowser.LocalMetadata.Parsers
item.LinkedChildren = list.ToArray();
}
-
- public PlaylistXmlParser(ILogger<PlaylistXmlParser> logger, IProviderManager providerManager)
- : base(logger, providerManager)
- {
- }
}
}
diff --git a/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs
index 2d115a591..cc705a9df 100644
--- a/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs
+++ b/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs
@@ -16,6 +16,12 @@ namespace MediaBrowser.LocalMetadata.Providers
private readonly ILogger<BoxSetXmlParser> _logger;
private readonly IProviderManager _providerManager;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BoxSetXmlProvider"/> class.
+ /// </summary>
+ /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+ /// <param name="logger">Instance of the <see cref="ILogger{BoxSetXmlParser}"/> interface.</param>
+ /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
public BoxSetXmlProvider(IFileSystem fileSystem, ILogger<BoxSetXmlParser> logger, IProviderManager providerManager)
: base(fileSystem)
{
@@ -23,11 +29,13 @@ namespace MediaBrowser.LocalMetadata.Providers
_providerManager = providerManager;
}
+ /// <inheritdoc />
protected override void Fetch(MetadataResult<BoxSet> result, string path, CancellationToken cancellationToken)
{
new BoxSetXmlParser(_logger, _providerManager).Fetch(result, path, cancellationToken);
}
+ /// <inheritdoc />
protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService)
{
return directoryService.GetFile(Path.Combine(info.Path, "collection.xml"));
diff --git a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs
index d4e2bc8e5..36f3048ad 100644
--- a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs
+++ b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs
@@ -8,11 +8,20 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.LocalMetadata.Providers
{
+ /// <summary>
+ /// Playlist xml provider.
+ /// </summary>
public class PlaylistXmlProvider : BaseXmlProvider<Playlist>
{
private readonly ILogger<PlaylistXmlParser> _logger;
private readonly IProviderManager _providerManager;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PlaylistXmlProvider"/> class.
+ /// </summary>
+ /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+ /// <param name="logger">Instance of the <see cref="ILogger{PlaylistXmlParser}"/> interface.</param>
+ /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
public PlaylistXmlProvider(
IFileSystem fileSystem,
ILogger<PlaylistXmlParser> logger,
@@ -23,14 +32,16 @@ namespace MediaBrowser.LocalMetadata.Providers
_providerManager = providerManager;
}
+ /// <inheritdoc />
protected override void Fetch(MetadataResult<Playlist> result, string path, CancellationToken cancellationToken)
{
new PlaylistXmlParser(_logger, _providerManager).Fetch(result, path, cancellationToken);
}
+ /// <inheritdoc />
protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService)
{
- return directoryService.GetFile(PlaylistXmlSaver.GetSavePath(info.Path, FileSystem));
+ return directoryService.GetFile(PlaylistXmlSaver.GetSavePath(info.Path));
}
}
}
diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
index 071902393..7a4823e1b 100644
--- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
+++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
@@ -17,10 +17,25 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.LocalMetadata.Savers
{
+ /// <inheritdoc />
public abstract class BaseXmlSaver : IMetadataFileSaver
{
- private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
+ /// <summary>
+ /// Gets the date added format.
+ /// </summary>
+ public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
+ private static readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BaseXmlSaver"/> class.
+ /// </summary>
+ /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+ /// <param name="configurationManager">Instance of the <see cref="IServerConfigurationManager"/> 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="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="logger">Instance of the <see cref="ILogger{BaseXmlSaver}"/> interface.</param>
public BaseXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger<BaseXmlSaver> logger)
{
FileSystem = fileSystem;
@@ -31,15 +46,40 @@ namespace MediaBrowser.LocalMetadata.Savers
Logger = logger;
}
+ /// <summary>
+ /// Gets the file system.
+ /// </summary>
protected IFileSystem FileSystem { get; private set; }
+
+ /// <summary>
+ /// Gets the configuration manager.
+ /// </summary>
protected IServerConfigurationManager ConfigurationManager { get; private set; }
+
+ /// <summary>
+ /// Gets the library manager.
+ /// </summary>
protected ILibraryManager LibraryManager { get; private set; }
+
+ /// <summary>
+ /// Gets the user manager.
+ /// </summary>
protected IUserManager UserManager { get; private set; }
+
+ /// <summary>
+ /// Gets the user data manager.
+ /// </summary>
protected IUserDataManager UserDataManager { get; private set; }
+
+ /// <summary>
+ /// Gets the logger.
+ /// </summary>
protected ILogger<BaseXmlSaver> Logger { get; private set; }
+ /// <inheritdoc />
public string Name => XmlProviderUtils.Name;
+ /// <inheritdoc />
public string GetSavePath(BaseItem item)
{
return GetLocalSavePath(item);
@@ -70,20 +110,19 @@ namespace MediaBrowser.LocalMetadata.Savers
/// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
public abstract bool IsEnabledFor(BaseItem item, ItemUpdateType updateType);
+ /// <inheritdoc />
public void Save(BaseItem item, CancellationToken cancellationToken)
{
var path = GetSavePath(item);
- using (var memoryStream = new MemoryStream())
- {
- Save(item, memoryStream, path);
+ using var memoryStream = new MemoryStream();
+ Save(item, memoryStream);
- memoryStream.Position = 0;
+ memoryStream.Position = 0;
- cancellationToken.ThrowIfCancellationRequested();
+ cancellationToken.ThrowIfCancellationRequested();
- SaveToFile(memoryStream, path);
- }
+ SaveToFile(memoryStream, path);
}
private void SaveToFile(Stream stream, string path)
@@ -115,7 +154,7 @@ namespace MediaBrowser.LocalMetadata.Savers
}
}
- private void Save(BaseItem item, Stream stream, string xmlPath)
+ private void Save(BaseItem item, Stream stream)
{
var settings = new XmlWriterSettings
{
@@ -136,7 +175,7 @@ namespace MediaBrowser.LocalMetadata.Savers
if (baseItem != null)
{
- AddCommonNodes(baseItem, writer, LibraryManager, UserManager, UserDataManager, FileSystem, ConfigurationManager);
+ AddCommonNodes(baseItem, writer, LibraryManager);
}
WriteCustomElements(item, writer);
@@ -147,22 +186,27 @@ namespace MediaBrowser.LocalMetadata.Savers
}
}
+ /// <summary>
+ /// Write custom elements.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="writer">The xml writer.</param>
protected abstract void WriteCustomElements(BaseItem item, XmlWriter writer);
- public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
-
/// <summary>
/// Adds the common nodes.
/// </summary>
- /// <returns>Task.</returns>
- public static void AddCommonNodes(BaseItem item, XmlWriter writer, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataRepo, IFileSystem fileSystem, IServerConfigurationManager config)
+ /// <param name="item">The item.</param>
+ /// <param name="writer">The xml writer.</param>
+ /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
+ public static void AddCommonNodes(BaseItem item, XmlWriter writer, ILibraryManager libraryManager)
{
if (!string.IsNullOrEmpty(item.OfficialRating))
{
writer.WriteElementString("ContentRating", item.OfficialRating);
}
- writer.WriteElementString("Added", item.DateCreated.ToLocalTime().ToString("G"));
+ writer.WriteElementString("Added", item.DateCreated.ToLocalTime().ToString("G", CultureInfo.InvariantCulture));
writer.WriteElementString("LockData", item.IsLocked.ToString(CultureInfo.InvariantCulture).ToLowerInvariant());
@@ -173,7 +217,7 @@ namespace MediaBrowser.LocalMetadata.Savers
if (item.CriticRating.HasValue)
{
- writer.WriteElementString("CriticRating", item.CriticRating.Value.ToString(UsCulture));
+ writer.WriteElementString("CriticRating", item.CriticRating.Value.ToString(_usCulture));
}
if (!string.IsNullOrEmpty(item.Overview))
@@ -185,6 +229,7 @@ namespace MediaBrowser.LocalMetadata.Savers
{
writer.WriteElementString("OriginalTitle", item.OriginalTitle);
}
+
if (!string.IsNullOrEmpty(item.CustomRating))
{
writer.WriteElementString("CustomRating", item.CustomRating);
@@ -205,11 +250,11 @@ namespace MediaBrowser.LocalMetadata.Savers
{
if (item is Person)
{
- writer.WriteElementString("BirthDate", item.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd"));
+ writer.WriteElementString("BirthDate", item.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
}
else if (!(item is Episode))
{
- writer.WriteElementString("PremiereDate", item.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd"));
+ writer.WriteElementString("PremiereDate", item.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
}
}
@@ -217,11 +262,11 @@ namespace MediaBrowser.LocalMetadata.Savers
{
if (item is Person)
{
- writer.WriteElementString("DeathDate", item.EndDate.Value.ToLocalTime().ToString("yyyy-MM-dd"));
+ writer.WriteElementString("DeathDate", item.EndDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
}
else if (!(item is Episode))
{
- writer.WriteElementString("EndDate", item.EndDate.Value.ToLocalTime().ToString("yyyy-MM-dd"));
+ writer.WriteElementString("EndDate", item.EndDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
}
}
@@ -257,12 +302,12 @@ namespace MediaBrowser.LocalMetadata.Savers
if (item.CommunityRating.HasValue)
{
- writer.WriteElementString("Rating", item.CommunityRating.Value.ToString(UsCulture));
+ writer.WriteElementString("Rating", item.CommunityRating.Value.ToString(_usCulture));
}
if (item.ProductionYear.HasValue && !(item is Person))
{
- writer.WriteElementString("ProductionYear", item.ProductionYear.Value.ToString(UsCulture));
+ writer.WriteElementString("ProductionYear", item.ProductionYear.Value.ToString(_usCulture));
}
var hasAspectRatio = item as IHasAspectRatio;
@@ -278,6 +323,7 @@ namespace MediaBrowser.LocalMetadata.Savers
{
writer.WriteElementString("Language", item.PreferredMetadataLanguage);
}
+
if (!string.IsNullOrEmpty(item.PreferredMetadataCountryCode))
{
writer.WriteElementString("CountryCode", item.PreferredMetadataCountryCode);
@@ -288,9 +334,9 @@ namespace MediaBrowser.LocalMetadata.Savers
if (runTimeTicks.HasValue)
{
- var timespan = TimeSpan.FromTicks(runTimeTicks.Value);
+ var timespan = TimeSpan.FromTicks(runTimeTicks!.Value);
- writer.WriteElementString("RunningTime", Math.Floor(timespan.TotalMinutes).ToString(UsCulture));
+ writer.WriteElementString("RunningTime", Math.Floor(timespan.TotalMinutes).ToString(_usCulture));
}
if (item.ProviderIds != null)
@@ -363,7 +409,7 @@ namespace MediaBrowser.LocalMetadata.Savers
if (person.SortOrder.HasValue)
{
- writer.WriteElementString("SortOrder", person.SortOrder.Value.ToString(UsCulture));
+ writer.WriteElementString("SortOrder", person.SortOrder.Value.ToString(_usCulture));
}
writer.WriteEndElement();
@@ -393,6 +439,11 @@ namespace MediaBrowser.LocalMetadata.Savers
AddMediaInfo(item, writer);
}
+ /// <summary>
+ /// Add shares.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="writer">The xml writer.</param>
public static void AddShares(IHasShares item, XmlWriter writer)
{
writer.WriteStartElement("Shares");
@@ -415,13 +466,13 @@ namespace MediaBrowser.LocalMetadata.Savers
/// <summary>
/// Appends the media info.
/// </summary>
- /// <typeparam name="T"></typeparam>
+ /// <param name="item">The item.</param>
+ /// <param name="writer">The xml writer.</param>
+ /// <typeparam name="T">Type of item.</typeparam>
public static void AddMediaInfo<T>(T item, XmlWriter writer)
where T : BaseItem
{
- var video = item as Video;
-
- if (video != null)
+ if (item is Video video)
{
if (video.Video3DFormat.HasValue)
{
@@ -447,6 +498,13 @@ namespace MediaBrowser.LocalMetadata.Savers
}
}
+ /// <summary>
+ /// ADd linked children.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="writer">The xml writer.</param>
+ /// <param name="pluralNodeName">The plural node name.</param>
+ /// <param name="singularNodeName">The singular node name.</param>
public static void AddLinkedChildren(Folder item, XmlWriter writer, string pluralNodeName, string singularNodeName)
{
var items = item.LinkedChildren
diff --git a/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs
index 1dc09bf18..b08387b0c 100644
--- a/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs
+++ b/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs
@@ -9,8 +9,26 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.LocalMetadata.Savers
{
+ /// <summary>
+ /// Box set xml saver.
+ /// </summary>
public class BoxSetXmlSaver : BaseXmlSaver
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BoxSetXmlSaver"/> class.
+ /// </summary>
+ /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+ /// <param name="configurationManager">Instance of the <see cref="IServerConfigurationManager"/> 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="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="logger">Instance of the <see cref="ILogger{BoxSetXmlSaver}"/> interface.</param>
+ public BoxSetXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger<BoxSetXmlSaver> logger)
+ : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger)
+ {
+ }
+
+ /// <inheritdoc />
public override bool IsEnabledFor(BaseItem item, ItemUpdateType updateType)
{
if (!item.SupportsLocalMetadata)
@@ -21,18 +39,15 @@ namespace MediaBrowser.LocalMetadata.Savers
return item is BoxSet && updateType >= ItemUpdateType.MetadataDownload;
}
+ /// <inheritdoc />
protected override void WriteCustomElements(BaseItem item, XmlWriter writer)
{
}
+ /// <inheritdoc />
protected override string GetLocalSavePath(BaseItem item)
{
return Path.Combine(item.Path, "collection.xml");
}
-
- public BoxSetXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger<BoxSetXmlSaver> logger)
- : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger)
- {
- }
}
}
diff --git a/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs
index bbb0a3501..c2f106423 100644
--- a/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs
+++ b/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs
@@ -9,6 +9,9 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.LocalMetadata.Savers
{
+ /// <summary>
+ /// Playlist xml saver.
+ /// </summary>
public class PlaylistXmlSaver : BaseXmlSaver
{
/// <summary>
@@ -16,6 +19,21 @@ namespace MediaBrowser.LocalMetadata.Savers
/// </summary>
public const string DefaultPlaylistFilename = "playlist.xml";
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PlaylistXmlSaver"/> class.
+ /// </summary>
+ /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+ /// <param name="configurationManager">Instance of the <see cref="IServerConfigurationManager"/> 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="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="logger">Instance of the <see cref="ILogger{PlaylistXmlSaver}"/> interface.</param>
+ public PlaylistXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger<PlaylistXmlSaver> logger)
+ : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger)
+ {
+ }
+
+ /// <inheritdoc />
public override bool IsEnabledFor(BaseItem item, ItemUpdateType updateType)
{
if (!item.SupportsLocalMetadata)
@@ -26,6 +44,7 @@ namespace MediaBrowser.LocalMetadata.Savers
return item is Playlist && updateType >= ItemUpdateType.MetadataImport;
}
+ /// <inheritdoc />
protected override void WriteCustomElements(BaseItem item, XmlWriter writer)
{
var game = (Playlist)item;
@@ -36,12 +55,18 @@ namespace MediaBrowser.LocalMetadata.Savers
}
}
+ /// <inheritdoc />
protected override string GetLocalSavePath(BaseItem item)
{
- return GetSavePath(item.Path, FileSystem);
+ return GetSavePath(item.Path);
}
- public static string GetSavePath(string itemPath, IFileSystem fileSystem)
+ /// <summary>
+ /// Get the save path.
+ /// </summary>
+ /// <param name="itemPath">The item path.</param>
+ /// <returns>The save path.</returns>
+ public static string GetSavePath(string itemPath)
{
var path = itemPath;
@@ -52,10 +77,5 @@ namespace MediaBrowser.LocalMetadata.Savers
return Path.Combine(path, DefaultPlaylistFilename);
}
-
- public PlaylistXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger<PlaylistXmlSaver> logger)
- : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger)
- {
- }
}
}
diff --git a/MediaBrowser.LocalMetadata/XmlProviderUtils.cs b/MediaBrowser.LocalMetadata/XmlProviderUtils.cs
new file mode 100644
index 000000000..e247b8bb8
--- /dev/null
+++ b/MediaBrowser.LocalMetadata/XmlProviderUtils.cs
@@ -0,0 +1,13 @@
+namespace MediaBrowser.LocalMetadata
+{
+ /// <summary>
+ /// The xml provider utils.
+ /// </summary>
+ public static class XmlProviderUtils
+ {
+ /// <summary>
+ /// Gets the name.
+ /// </summary>
+ public static string Name => "Emby Xml";
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
index a82f2108f..f02999370 100644
--- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
+++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
@@ -269,7 +269,6 @@ namespace MediaBrowser.MediaEncoding.Attachments
if (disposing)
{
-
}
_disposed = true;
diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs
index 3260f3051..ccfae2fa5 100644
--- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs
+++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs
@@ -9,7 +9,7 @@ using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.MediaEncoding.BdInfo
{
/// <summary>
- /// Class BdInfoExaminer
+ /// Class BdInfoExaminer.
/// </summary>
public class BdInfoExaminer : IBlurayExaminer
{
@@ -41,7 +41,7 @@ namespace MediaBrowser.MediaEncoding.BdInfo
var outputStream = new BlurayDiscInfo
{
- MediaStreams = new MediaStream[] { }
+ MediaStreams = Array.Empty<MediaStream>()
};
if (playlist == null)
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index 6e036d24c..0fd0239b4 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -14,23 +14,45 @@ namespace MediaBrowser.MediaEncoding.Encoder
private static readonly string[] requiredDecoders = new[]
{
+ "h264",
+ "hevc",
"mpeg2video",
+ "mpeg4",
+ "msmpeg4",
+ "dts",
+ "ac3",
+ "aac",
+ "mp3",
"h264_qsv",
"hevc_qsv",
"mpeg2_qsv",
- "mpeg2_mmal",
- "mpeg4_mmal",
"vc1_qsv",
- "vc1_mmal",
+ "vp8_qsv",
+ "vp9_qsv",
"h264_cuvid",
"hevc_cuvid",
- "dts",
- "ac3",
- "aac",
- "mp3",
- "h264",
+ "mpeg2_cuvid",
+ "vc1_cuvid",
+ "mpeg4_cuvid",
+ "vp8_cuvid",
+ "vp9_cuvid",
"h264_mmal",
- "hevc"
+ "mpeg2_mmal",
+ "mpeg4_mmal",
+ "vc1_mmal",
+ "h264_mediacodec",
+ "hevc_mediacodec",
+ "mpeg2_mediacodec",
+ "mpeg4_mediacodec",
+ "vp8_mediacodec",
+ "vp9_mediacodec",
+ "h264_opencl",
+ "hevc_opencl",
+ "mpeg2_opencl",
+ "mpeg4_opencl",
+ "vp8_opencl",
+ "vp9_opencl",
+ "vc1_opencl"
};
private static readonly string[] requiredEncoders = new[]
@@ -43,22 +65,24 @@ namespace MediaBrowser.MediaEncoding.Encoder
"libvpx-vp9",
"aac",
"libfdk_aac",
+ "ac3",
"libmp3lame",
"libopus",
"libvorbis",
"srt",
- "h264_nvenc",
- "hevc_nvenc",
+ "h264_amf",
+ "hevc_amf",
"h264_qsv",
"hevc_qsv",
- "h264_omx",
- "hevc_omx",
+ "h264_nvenc",
+ "hevc_nvenc",
"h264_vaapi",
"hevc_vaapi",
+ "h264_omx",
+ "hevc_omx",
"h264_v4l2m2m",
- "ac3",
- "h264_amf",
- "hevc_amf"
+ "h264_videotoolbox",
+ "hevc_videotoolbox"
};
// Try and use the individual library versions to determine a FFmpeg version
@@ -66,6 +90,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
// $ ffmpeg -version | perl -ne ' print "$1=$2.$3," if /^(lib\w+)\s+(\d+)\.\s*(\d+)/'
private static readonly IReadOnlyDictionary<string, Version> _ffmpegVersionMap = new Dictionary<string, Version>
{
+ { "libavutil=56.51,libavcodec=58.91,libavformat=58.45,libavdevice=58.10,libavfilter=7.85,libswscale=5.7,libswresample=3.7,libpostproc=55.7,", new Version(4, 3) },
{ "libavutil=56.31,libavcodec=58.54,libavformat=58.29,libavdevice=58.8,libavfilter=7.57,libswscale=5.5,libswresample=3.5,libpostproc=55.5,", new Version(4, 2) },
{ "libavutil=56.22,libavcodec=58.35,libavformat=58.20,libavdevice=58.5,libavfilter=7.40,libswscale=5.3,libswresample=3.3,libpostproc=55.3,", new Version(4, 1) },
{ "libavutil=56.14,libavcodec=58.18,libavformat=58.12,libavdevice=58.3,libavfilter=7.16,libswscale=5.1,libswresample=3.1,libpostproc=55.1,", new Version(4, 0) },
@@ -159,6 +184,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
public IEnumerable<string> GetEncoders() => GetCodecs(Codec.Encoder);
+ public IEnumerable<string> GetHwaccels() => GetHwaccelTypes();
+
/// <summary>
/// Using the output from "ffmpeg -version" work out the FFmpeg version.
/// For pre-built binaries the first line should contain a string like "ffmpeg version x.y", which is easy
@@ -218,6 +245,29 @@ namespace MediaBrowser.MediaEncoding.Encoder
Decoder
}
+ private IEnumerable<string> GetHwaccelTypes()
+ {
+ string output = null;
+ try
+ {
+ output = GetProcessOutput(_encoderPath, "-hwaccels");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error detecting available hwaccel types");
+ }
+
+ if (string.IsNullOrWhiteSpace(output))
+ {
+ return Enumerable.Empty<string>();
+ }
+
+ var found = output.Split(new char[] {'\r','\n'}, StringSplitOptions.RemoveEmptyEntries).Skip(1).Distinct().ToList();
+ _logger.LogInformation("Available hwaccel types: {Types}", found);
+
+ return found;
+ }
+
private IEnumerable<string> GetCodecs(Codec codec)
{
string codecstr = codec == Codec.Encoder ? "encoders" : "decoders";
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 26bd202ba..62fdbc618 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -21,11 +21,12 @@ using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
+using Microsoft.Extensions.Configuration;
namespace MediaBrowser.MediaEncoding.Encoder
{
/// <summary>
- /// Class MediaEncoder
+ /// Class MediaEncoder.
/// </summary>
public class MediaEncoder : IMediaEncoder, IDisposable
{
@@ -46,7 +47,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
private readonly object _runningProcessesLock = new object();
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
- private string _ffmpegPath;
+ private string _ffmpegPath = string.Empty;
private string _ffprobePath;
public MediaEncoder(
@@ -55,14 +56,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
IFileSystem fileSystem,
ILocalizationManager localization,
Lazy<EncodingHelper> encodingHelperFactory,
- string startupOptionsFFmpegPath)
+ IConfiguration config)
{
_logger = logger;
_configurationManager = configurationManager;
_fileSystem = fileSystem;
_localization = localization;
_encodingHelperFactory = encodingHelperFactory;
- _startupOptionFFmpegPath = startupOptionsFFmpegPath;
+ _startupOptionFFmpegPath = config.GetValue<string>(Controller.Extensions.ConfigurationExtensions.FfmpegPathKey) ?? string.Empty;
}
private EncodingHelper EncodingHelper => _encodingHelperFactory.Value;
@@ -111,6 +112,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
SetAvailableDecoders(validator.GetDecoders());
SetAvailableEncoders(validator.GetEncoders());
+ SetAvailableHwaccels(validator.GetHwaccels());
}
_logger.LogInformation("FFmpeg: {EncoderLocation}: {FfmpegPath}", EncoderLocation, _ffmpegPath ?? string.Empty);
@@ -165,7 +167,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// Validates the supplied FQPN to ensure it is a ffmpeg utility.
/// If checks pass, global variable FFmpegPath and EncoderLocation are updated.
/// </summary>
- /// <param name="path">FQPN to test</param>
+ /// <param name="path">FQPN to test.</param>
/// <param name="location">Location (External, Custom, System) of tool</param>
/// <returns></returns>
private bool ValidatePath(string path, FFmpegLocation location)
@@ -228,6 +230,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
return inJellyfinPath;
}
+
var values = Environment.GetEnvironmentVariable("PATH");
foreach (var path in values.Split(Path.PathSeparator))
@@ -247,14 +250,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
public void SetAvailableEncoders(IEnumerable<string> list)
{
_encoders = list.ToList();
- //_logger.Info("Supported encoders: {0}", string.Join(",", list.ToArray()));
+ // _logger.Info("Supported encoders: {0}", string.Join(",", list.ToArray()));
}
private List<string> _decoders = new List<string>();
public void SetAvailableDecoders(IEnumerable<string> list)
{
_decoders = list.ToList();
- //_logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray()));
+ // _logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray()));
+ }
+
+ private List<string> _hwaccels = new List<string>();
+ public void SetAvailableHwaccels(IEnumerable<string> list)
+ {
+ _hwaccels = list.ToList();
+ //_logger.Info("Supported hwaccels: {0}", string.Join(",", list.ToArray()));
}
public bool SupportsEncoder(string encoder)
@@ -267,6 +277,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
return _decoders.Contains(decoder, StringComparer.OrdinalIgnoreCase);
}
+ public bool SupportsHwaccel(string hwaccel)
+ {
+ return _hwaccels.Contains(hwaccel, StringComparer.OrdinalIgnoreCase);
+ }
+
public bool CanEncodeToAudioCodec(string codec)
{
if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
@@ -425,7 +440,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
/// <summary>
- /// The us culture
+ /// The us culture.
/// </summary>
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
@@ -500,11 +515,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
break;
case Video3DFormat.FullSideBySide:
vf = "crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2";
- //fsbs crop width in half,set the display aspect,crop out any black bars we may have made the scale width to 600.
+ // fsbs crop width in half,set the display aspect,crop out any black bars we may have made the scale width to 600.
break;
case Video3DFormat.HalfTopAndBottom:
vf = "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2";
- //htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600
+ // htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600
break;
case Video3DFormat.FullTopAndBottom:
vf = "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2";
@@ -920,7 +935,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
var fileParts = _fileSystem.GetFileNameWithoutExtension(f).Split('_');
return fileParts.Length == 3 && string.Equals(title, fileParts[1], StringComparison.OrdinalIgnoreCase);
-
}).ToList();
// If this resulted in not getting any vobs, just take them all
diff --git a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
index 78dc7b607..3aa296f7f 100644
--- a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
+++ b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
@@ -35,7 +35,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
/// <summary>
- /// Gets a string from an FFProbeResult tags dictionary
+ /// Gets a string from an FFProbeResult tags dictionary.
/// </summary>
/// <param name="tags">The tags.</param>
/// <param name="key">The key.</param>
@@ -52,7 +52,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
/// <summary>
- /// Gets an int from an FFProbeResult tags dictionary
+ /// Gets an int from an FFProbeResult tags dictionary.
/// </summary>
/// <param name="tags">The tags.</param>
/// <param name="key">The key.</param>
@@ -73,7 +73,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
/// <summary>
- /// Gets a DateTime from an FFProbeResult tags dictionary
+ /// Gets a DateTime from an FFProbeResult tags dictionary.
/// </summary>
/// <param name="tags">The tags.</param>
/// <param name="key">The key.</param>
@@ -94,7 +94,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
/// <summary>
- /// Converts a dictionary to case insensitive
+ /// Converts a dictionary to case insensitive.
/// </summary>
/// <param name="dict">The dict.</param>
/// <returns>Dictionary{System.StringSystem.String}.</returns>
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 9386d6524..c85d963ed 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -93,6 +93,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
overview = FFProbeHelpers.GetDictionaryValue(tags, "description");
}
+
if (string.IsNullOrWhiteSpace(overview))
{
overview = FFProbeHelpers.GetDictionaryValue(tags, "desc");
@@ -274,10 +275,12 @@ namespace MediaBrowser.MediaEncoding.Probing
reader.Read();
continue;
}
+
using (var subtree = reader.ReadSubtree())
{
ReadFromDictNode(subtree, info);
}
+
break;
default:
reader.Skip();
@@ -319,6 +322,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
ProcessPairs(currentKey, pairs, info);
}
+
currentKey = reader.ReadElementContentAsString();
pairs = new List<NameValuePair>();
break;
@@ -332,6 +336,7 @@ namespace MediaBrowser.MediaEncoding.Probing
Value = value
});
}
+
break;
case "array":
if (reader.IsEmptyElement)
@@ -339,6 +344,7 @@ namespace MediaBrowser.MediaEncoding.Probing
reader.Read();
continue;
}
+
using (var subtree = reader.ReadSubtree())
{
if (!string.IsNullOrWhiteSpace(currentKey))
@@ -346,6 +352,7 @@ namespace MediaBrowser.MediaEncoding.Probing
pairs.AddRange(ReadValueArray(subtree));
}
}
+
break;
default:
reader.Skip();
@@ -381,6 +388,7 @@ namespace MediaBrowser.MediaEncoding.Probing
reader.Read();
continue;
}
+
using (var subtree = reader.ReadSubtree())
{
var dict = GetNameValuePair(subtree);
@@ -389,6 +397,7 @@ namespace MediaBrowser.MediaEncoding.Probing
pairs.Add(dict);
}
}
+
break;
default:
reader.Skip();
@@ -413,7 +422,6 @@ namespace MediaBrowser.MediaEncoding.Probing
.Where(i => !string.IsNullOrWhiteSpace(i))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
-
}
else if (string.Equals(key, "screenwriters", StringComparison.OrdinalIgnoreCase))
{
@@ -425,7 +433,6 @@ namespace MediaBrowser.MediaEncoding.Probing
Type = PersonType.Writer
});
}
-
}
else if (string.Equals(key, "producers", StringComparison.OrdinalIgnoreCase))
{
@@ -517,7 +524,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
/// <summary>
- /// Converts ffprobe stream info to our MediaAttachment class
+ /// Converts ffprobe stream info to our MediaAttachment class.
/// </summary>
/// <param name="streamInfo">The stream info.</param>
/// <returns>MediaAttachments.</returns>
@@ -550,7 +557,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
/// <summary>
- /// Converts ffprobe stream info to our MediaStream class
+ /// Converts ffprobe stream info to our MediaStream class.
/// </summary>
/// <param name="isAudio">if set to <c>true</c> [is info].</param>
/// <param name="streamInfo">The stream info.</param>
@@ -562,7 +569,7 @@ namespace MediaBrowser.MediaEncoding.Probing
if (string.Equals(streamInfo.CodecName, "mov_text", StringComparison.OrdinalIgnoreCase))
{
// Edit: but these are also sometimes subtitles?
- //return null;
+ // return null;
}
var stream = new MediaStream
@@ -684,7 +691,7 @@ namespace MediaBrowser.MediaEncoding.Probing
stream.BitDepth = streamInfo.BitsPerRawSample;
}
- //stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) ||
+ // stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) ||
// string.Equals(stream.AspectRatio, "2.35:1", StringComparison.OrdinalIgnoreCase) ||
// string.Equals(stream.AspectRatio, "2.40:1", StringComparison.OrdinalIgnoreCase);
@@ -769,7 +776,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
/// <summary>
- /// Gets a string from an FFProbeResult tags dictionary
+ /// Gets a string from an FFProbeResult tags dictionary.
/// </summary>
/// <param name="tags">The tags.</param>
/// <param name="key">The key.</param>
@@ -950,11 +957,12 @@ namespace MediaBrowser.MediaEncoding.Probing
{
peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Composer });
}
+
audio.People = peoples.ToArray();
}
- //var conductor = FFProbeHelpers.GetDictionaryValue(tags, "conductor");
- //if (!string.IsNullOrWhiteSpace(conductor))
+ // var conductor = FFProbeHelpers.GetDictionaryValue(tags, "conductor");
+ // if (!string.IsNullOrWhiteSpace(conductor))
//{
// foreach (var person in Split(conductor, false))
// {
@@ -962,8 +970,8 @@ namespace MediaBrowser.MediaEncoding.Probing
// }
//}
- //var lyricist = FFProbeHelpers.GetDictionaryValue(tags, "lyricist");
- //if (!string.IsNullOrWhiteSpace(lyricist))
+ // var lyricist = FFProbeHelpers.GetDictionaryValue(tags, "lyricist");
+ // if (!string.IsNullOrWhiteSpace(lyricist))
//{
// foreach (var person in Split(lyricist, false))
// {
@@ -981,6 +989,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Writer });
}
+
audio.People = peoples.ToArray();
}
@@ -999,7 +1008,7 @@ namespace MediaBrowser.MediaEncoding.Probing
var artist = FFProbeHelpers.GetDictionaryValue(tags, "artist");
if (string.IsNullOrWhiteSpace(artist))
{
- audio.Artists = new string[] { };
+ audio.Artists = Array.Empty<string>();
}
else
{
@@ -1014,6 +1023,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album artist");
}
+
if (string.IsNullOrWhiteSpace(albumArtist))
{
albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album_artist");
@@ -1021,14 +1031,13 @@ namespace MediaBrowser.MediaEncoding.Probing
if (string.IsNullOrWhiteSpace(albumArtist))
{
- audio.AlbumArtists = new string[] { };
+ audio.AlbumArtists = Array.Empty<string>();
}
else
{
audio.AlbumArtists = SplitArtists(albumArtist, _nameDelimiters, true)
.DistinctNames()
.ToArray();
-
}
if (audio.AlbumArtists.Length == 0)
@@ -1056,23 +1065,43 @@ namespace MediaBrowser.MediaEncoding.Probing
// These support mulitple values, but for now we only store the first.
var mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Artist Id"));
- if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMARTISTID"));
+ if (mb == null)
+ {
+ mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMARTISTID"));
+ }
+
audio.SetProviderId(MetadataProvider.MusicBrainzAlbumArtist, mb);
mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Artist Id"));
- if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ARTISTID"));
+ if (mb == null)
+ {
+ mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ARTISTID"));
+ }
+
audio.SetProviderId(MetadataProvider.MusicBrainzArtist, mb);
mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Id"));
- if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMID"));
+ if (mb == null)
+ {
+ mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMID"));
+ }
+
audio.SetProviderId(MetadataProvider.MusicBrainzAlbum, mb);
mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Group Id"));
- if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASEGROUPID"));
+ if (mb == null)
+ {
+ mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASEGROUPID"));
+ }
+
audio.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, mb);
mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Track Id"));
- if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASETRACKID"));
+ if (mb == null)
+ {
+ mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASETRACKID"));
+ }
+
audio.SetProviderId(MetadataProvider.MusicBrainzTrack, mb);
}
@@ -1157,7 +1186,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
/// <summary>
- /// Gets the studios from the tags collection
+ /// Gets the studios from the tags collection.
/// </summary>
/// <param name="info">The info.</param>
/// <param name="tags">The tags.</param>
@@ -1178,6 +1207,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
continue;
}
+
if (info.AlbumArtists.Contains(studio, StringComparer.OrdinalIgnoreCase))
{
continue;
@@ -1194,7 +1224,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
/// <summary>
- /// Gets the genres from the tags collection
+ /// Gets the genres from the tags collection.
/// </summary>
/// <param name="info">The information.</param>
/// <param name="tags">The tags.</param>
@@ -1354,14 +1384,18 @@ namespace MediaBrowser.MediaEncoding.Probing
description = string.Join(" ", numbers, 1, numbers.Length - 1).Trim(); // Skip the first, concatenate the rest, clean up spaces and save it
}
else
+ {
throw new Exception(); // Switch to default parsing
+ }
}
catch // Default parsing
{
if (subtitle.Contains(".")) // skip the comment, keep the subtitle
description = string.Join(".", subtitle.Split('.'), 1, subtitle.Split('.').Length - 1).Trim(); // skip the first
else
+ {
description = subtitle.Trim(); // Clean up whitespaces and save it
+ }
}
}
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
index f44cf1452..0e2d70017 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
@@ -23,6 +23,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
string line;
while (reader.ReadLine() != "[Events]")
{ }
+
var headers = ParseFieldHeaders(reader.ReadLine());
while ((line = reader.ReadLine()) != null)
@@ -56,6 +57,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
trackEvents.Add(subEvent);
}
}
+
trackInfo.TrackEvents = trackEvents.ToArray();
return trackInfo;
}
@@ -112,11 +114,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
pre = s.Substring(0, 5) + "}";
}
+
int indexOfEnd = p.Text.IndexOf('}');
p.Text = p.Text.Remove(indexOfBegin, (indexOfEnd - indexOfBegin) + 1);
indexOfBegin = p.Text.IndexOf('{');
}
+
p.Text = pre + p.Text;
}
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs
index c98dd1502..728efa788 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs
@@ -35,6 +35,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
continue;
}
+
var subEvent = new SubtitleTrackEvent { Id = line };
line = reader.ReadLine();
@@ -52,11 +53,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles
_logger.LogWarning("Unrecognized line in srt: {0}", line);
continue;
}
+
subEvent.StartPositionTicks = GetTicks(time[0]);
var endTime = time[1];
var idx = endTime.IndexOf(" ", StringComparison.Ordinal);
if (idx > 0)
+ {
endTime = endTime.Substring(0, idx);
+ }
+
subEvent.EndPositionTicks = GetTicks(endTime);
var multiline = new List<string>();
while ((line = reader.ReadLine()) != null)
@@ -65,8 +70,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
break;
}
+
multiline.Add(line);
}
+
subEvent.Text = string.Join(ParserValues.NewLine, multiline);
subEvent.Text = subEvent.Text.Replace(@"\N", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase);
subEvent.Text = Regex.Replace(subEvent.Text, @"\{(?:\\\d?[\w.-]+(?:\([^\)]*\)|&H?[0-9A-Fa-f]+&|))+\}", string.Empty, RegexOptions.IgnoreCase);
@@ -76,6 +83,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
trackEvents.Add(subEvent);
}
}
+
trackInfo.TrackEvents = trackEvents.ToArray();
return trackInfo;
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
index b94d45165..77033c6b4 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
@@ -41,7 +41,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
lineNumber++;
if (!eventsStarted)
+ {
header.AppendLine(line);
+ }
if (line.Trim().ToLowerInvariant() == "[events]")
{
@@ -62,17 +64,29 @@ namespace MediaBrowser.MediaEncoding.Subtitles
for (int i = 0; i < format.Length; i++)
{
if (format[i].Trim().ToLowerInvariant() == "layer")
+ {
indexLayer = i;
+ }
else if (format[i].Trim().ToLowerInvariant() == "start")
+ {
indexStart = i;
+ }
else if (format[i].Trim().ToLowerInvariant() == "end")
+ {
indexEnd = i;
+ }
else if (format[i].Trim().ToLowerInvariant() == "text")
+ {
indexText = i;
+ }
else if (format[i].Trim().ToLowerInvariant() == "effect")
+ {
indexEffect = i;
+ }
else if (format[i].Trim().ToLowerInvariant() == "style")
+ {
indexStyle = i;
+ }
}
}
}
@@ -89,28 +103,48 @@ namespace MediaBrowser.MediaEncoding.Subtitles
string[] splittedLine;
if (s.StartsWith("dialogue:"))
+ {
splittedLine = line.Substring(10).Split(',');
+ }
else
+ {
splittedLine = line.Split(',');
+ }
for (int i = 0; i < splittedLine.Length; i++)
{
if (i == indexStart)
+ {
start = splittedLine[i].Trim();
+ }
else if (i == indexEnd)
+ {
end = splittedLine[i].Trim();
+ }
else if (i == indexLayer)
+ {
layer = splittedLine[i];
+ }
else if (i == indexEffect)
+ {
effect = splittedLine[i];
+ }
else if (i == indexText)
+ {
text = splittedLine[i];
+ }
else if (i == indexStyle)
+ {
style = splittedLine[i];
+ }
else if (i == indexName)
+ {
name = splittedLine[i];
+ }
else if (i > indexText)
+ {
text += "," + splittedLine[i];
+ }
}
try
@@ -130,11 +164,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
}
- //if (header.Length > 0)
- //subtitle.Header = header.ToString();
+ // if (header.Length > 0)
+ // subtitle.Header = header.ToString();
- //subtitle.Renumber(1);
+ // subtitle.Renumber(1);
}
+
trackInfo.TrackEvents = trackEvents.ToArray();
return trackInfo;
}
@@ -168,15 +203,23 @@ namespace MediaBrowser.MediaEncoding.Subtitles
CheckAndAddSubTags(ref fontName, ref extraTags, out italic);
text = text.Remove(start, end - start + 1);
if (italic)
+ {
text = text.Insert(start, "<font face=\"" + fontName + "\"" + extraTags + "><i>");
+ }
else
+ {
text = text.Insert(start, "<font face=\"" + fontName + "\"" + extraTags + ">");
+ }
int indexOfEndTag = text.IndexOf("{\\fn}", start);
if (indexOfEndTag > 0)
+ {
text = text.Remove(indexOfEndTag, "{\\fn}".Length).Insert(indexOfEndTag, "</font>");
+ }
else
+ {
text += "</font>";
+ }
}
}
@@ -193,15 +236,23 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
text = text.Remove(start, end - start + 1);
if (italic)
+ {
text = text.Insert(start, "<font size=\"" + fontSize + "\"" + extraTags + "><i>");
+ }
else
+ {
text = text.Insert(start, "<font size=\"" + fontSize + "\"" + extraTags + ">");
+ }
int indexOfEndTag = text.IndexOf("{\\fs}", start);
if (indexOfEndTag > 0)
+ {
text = text.Remove(indexOfEndTag, "{\\fs}".Length).Insert(indexOfEndTag, "</font>");
+ }
else
+ {
text += "</font>";
+ }
}
}
}
@@ -225,14 +276,22 @@ namespace MediaBrowser.MediaEncoding.Subtitles
text = text.Remove(start, end - start + 1);
if (italic)
+ {
text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + "><i>");
+ }
else
+ {
text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
+ }
int indexOfEndTag = text.IndexOf("{\\c}", start);
if (indexOfEndTag > 0)
+ {
text = text.Remove(indexOfEndTag, "{\\c}".Length).Insert(indexOfEndTag, "</font>");
+ }
else
+ {
text += "</font>";
+ }
}
}
@@ -255,32 +314,41 @@ namespace MediaBrowser.MediaEncoding.Subtitles
text = text.Remove(start, end - start + 1);
if (italic)
+ {
text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + "><i>");
+ }
else
+ {
text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
+ }
text += "</font>";
}
}
-
}
text = text.Replace(@"{\i1}", "<i>");
text = text.Replace(@"{\i0}", "</i>");
text = text.Replace(@"{\i}", "</i>");
if (CountTagInText(text, "<i>") > CountTagInText(text, "</i>"))
+ {
text += "</i>";
+ }
text = text.Replace(@"{\u1}", "<u>");
text = text.Replace(@"{\u0}", "</u>");
text = text.Replace(@"{\u}", "</u>");
if (CountTagInText(text, "<u>") > CountTagInText(text, "</u>"))
+ {
text += "</u>";
+ }
text = text.Replace(@"{\b1}", "<b>");
text = text.Replace(@"{\b0}", "</b>");
text = text.Replace(@"{\b}", "</b>");
if (CountTagInText(text, "<b>") > CountTagInText(text, "</b>"))
+ {
text += "</b>";
+ }
return text;
}
@@ -288,7 +356,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles
private static bool IsInteger(string s)
{
if (int.TryParse(s, out var i))
+ {
return true;
+ }
+
return false;
}
@@ -300,9 +371,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
count++;
if (index == text.Length)
+ {
return count;
+ }
+
index = text.IndexOf(tag, index + 1);
}
+
return count;
}
@@ -330,6 +405,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
rest = string.Empty;
}
+
extraTags += " size=\"" + fontSize.Substring(2) + "\"";
}
else if (rest.StartsWith("fn") && rest.Length > 2)
@@ -345,6 +421,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
rest = string.Empty;
}
+
extraTags += " face=\"" + fontName.Substring(2) + "\"";
}
else if (rest.StartsWith("c") && rest.Length > 2)
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index f08af6045..f1aa8ea5f 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -115,6 +115,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
throw new ArgumentNullException(nameof(item));
}
+
if (string.IsNullOrWhiteSpace(mediaSourceId))
{
throw new ArgumentNullException(nameof(mediaSourceId));
@@ -271,8 +272,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
public string Path { get; set; }
+
public MediaProtocol Protocol { get; set; }
+
public string Format { get; set; }
+
public bool IsExternal { get; set; }
}
@@ -287,10 +291,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
return new SrtParser(_logger);
}
+
if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase))
{
return new SsaParser();
}
+
if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase))
{
return new AssParser();
@@ -315,14 +321,17 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
return new JsonWriter();
}
+
if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase))
{
return new SrtWriter();
}
+
if (string.Equals(format, SubtitleFormat.VTT, StringComparison.OrdinalIgnoreCase))
{
return new VttWriter();
}
+
if (string.Equals(format, SubtitleFormat.TTML, StringComparison.OrdinalIgnoreCase))
{
return new TtmlWriter();
@@ -344,7 +353,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
/// <summary>
- /// The _semaphoreLocks
+ /// The _semaphoreLocks.
/// </summary>
private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks =
new ConcurrentDictionary<string, SemaphoreSlim>();
@@ -640,7 +649,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
catch (FileNotFoundException)
{
-
}
catch (IOException ex)
{
@@ -737,7 +745,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName;
// UTF16 is automatically converted to UTF8 by FFmpeg, do not specify a character encoding
- if ((path.EndsWith(".ass") || path.EndsWith(".ssa"))
+ if ((path.EndsWith(".ass") || path.EndsWith(".ssa") || path.EndsWith(".srt"))
&& (string.Equals(charset, "utf-16le", StringComparison.OrdinalIgnoreCase)
|| string.Equals(charset, "utf-16be", StringComparison.OrdinalIgnoreCase)))
{
diff --git a/MediaBrowser.Model/Channels/ChannelFeatures.cs b/MediaBrowser.Model/Channels/ChannelFeatures.cs
index 496102d83..a55754edd 100644
--- a/MediaBrowser.Model/Channels/ChannelFeatures.cs
+++ b/MediaBrowser.Model/Channels/ChannelFeatures.cs
@@ -38,7 +38,7 @@ namespace MediaBrowser.Model.Channels
public ChannelMediaContentType[] ContentTypes { get; set; }
/// <summary>
- /// Represents the maximum number of records the channel allows retrieving at a time
+ /// Represents the maximum number of records the channel allows retrieving at a time.
/// </summary>
public int? MaxPageSize { get; set; }
diff --git a/MediaBrowser.Model/Channels/ChannelQuery.cs b/MediaBrowser.Model/Channels/ChannelQuery.cs
index d11260039..fd90e7f06 100644
--- a/MediaBrowser.Model/Channels/ChannelQuery.cs
+++ b/MediaBrowser.Model/Channels/ChannelQuery.cs
@@ -10,7 +10,7 @@ namespace MediaBrowser.Model.Channels
public class ChannelQuery
{
/// <summary>
- /// Fields to return within the items, in addition to basic information
+ /// Fields to return within the items, in addition to basic information.
/// </summary>
/// <value>The fields.</value>
public ItemFields[] Fields { get; set; }
@@ -34,7 +34,7 @@ namespace MediaBrowser.Model.Channels
public int? StartIndex { get; set; }
/// <summary>
- /// The maximum number of items to return
+ /// The maximum number of items to return.
/// </summary>
/// <value>The limit.</value>
public int? Limit { get; set; }
diff --git a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs
index cdd322c94..66f3e1a94 100644
--- a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs
@@ -12,7 +12,15 @@ namespace MediaBrowser.Model.Configuration
public class BaseApplicationConfiguration
{
/// <summary>
- /// The number of days we should retain log files
+ /// Initializes a new instance of the <see cref="BaseApplicationConfiguration" /> class.
+ /// </summary>
+ public BaseApplicationConfiguration()
+ {
+ LogFileRetentionDays = 3;
+ }
+
+ /// <summary>
+ /// Gets or sets the number of days we should retain log files.
/// </summary>
/// <value>The log file retention days.</value>
public int LogFileRetentionDays { get; set; }
@@ -30,29 +38,21 @@ namespace MediaBrowser.Model.Configuration
public string CachePath { get; set; }
/// <summary>
- /// Last known version that was ran using the configuration.
+ /// Gets or sets the last known version that was ran using the configuration.
/// </summary>
/// <value>The version from previous run.</value>
[XmlIgnore]
public Version PreviousVersion { get; set; }
/// <summary>
- /// Stringified PreviousVersion to be stored/loaded,
- /// because System.Version itself isn't xml-serializable
+ /// Gets or sets the stringified PreviousVersion to be stored/loaded,
+ /// because System.Version itself isn't xml-serializable.
/// </summary>
- /// <value>String value of PreviousVersion</value>
+ /// <value>String value of PreviousVersion.</value>
public string PreviousVersionStr
{
get => PreviousVersion?.ToString();
set => PreviousVersion = Version.Parse(value);
}
-
- /// <summary>
- /// Initializes a new instance of the <see cref="BaseApplicationConfiguration" /> class.
- /// </summary>
- public BaseApplicationConfiguration()
- {
- LogFileRetentionDays = 3;
- }
}
}
diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs
index 0c0e01f11..9a30f7e9f 100644
--- a/MediaBrowser.Model/Configuration/EncodingOptions.cs
+++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs
@@ -37,6 +37,10 @@ namespace MediaBrowser.Model.Configuration
public string DeinterlaceMethod { get; set; }
+ public bool EnableDecodingColorDepth10Hevc { get; set; }
+
+ public bool EnableDecodingColorDepth10Vp9 { get; set; }
+
public bool EnableHardwareEncoding { get; set; }
public bool EnableSubtitleExtraction { get; set; }
@@ -54,6 +58,8 @@ namespace MediaBrowser.Model.Configuration
H264Crf = 23;
H265Crf = 28;
DeinterlaceMethod = "yadif";
+ EnableDecodingColorDepth10Hevc = true;
+ EnableDecodingColorDepth10Vp9 = true;
EnableHardwareEncoding = true;
EnableSubtitleExtraction = true;
HardwareDecodingCodecs = new string[] { "h264", "vc1" };
diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs
index 4229a4335..890469d36 100644
--- a/MediaBrowser.Model/Configuration/LibraryOptions.cs
+++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs
@@ -10,17 +10,27 @@ namespace MediaBrowser.Model.Configuration
public class LibraryOptions
{
public bool EnablePhotos { get; set; }
+
public bool EnableRealtimeMonitor { get; set; }
+
public bool EnableChapterImageExtraction { get; set; }
+
public bool ExtractChapterImagesDuringLibraryScan { get; set; }
+
public bool DownloadImagesInAdvance { get; set; }
+
public MediaPathInfo[] PathInfos { get; set; }
public bool SaveLocalMetadata { get; set; }
+
public bool EnableInternetProviders { get; set; }
+
public bool ImportMissingEpisodes { get; set; }
+
public bool EnableAutomaticSeriesGrouping { get; set; }
+
public bool EnableEmbeddedTitles { get; set; }
+
public bool EnableEmbeddedEpisodeInfos { get; set; }
public int AutomaticRefreshIntervalDays { get; set; }
@@ -38,17 +48,25 @@ namespace MediaBrowser.Model.Configuration
public string MetadataCountryCode { get; set; }
public string SeasonZeroDisplayName { get; set; }
+
public string[] MetadataSavers { get; set; }
+
public string[] DisabledLocalMetadataReaders { get; set; }
+
public string[] LocalMetadataReaderOrder { get; set; }
public string[] DisabledSubtitleFetchers { get; set; }
+
public string[] SubtitleFetcherOrder { get; set; }
public bool SkipSubtitlesIfEmbeddedSubtitlesPresent { get; set; }
+
public bool SkipSubtitlesIfAudioTrackMatches { get; set; }
+
public string[] SubtitleDownloadLanguages { get; set; }
+
public bool RequirePerfectSubtitleMatch { get; set; }
+
public bool SaveSubtitlesWithMedia { get; set; }
public TypeOptions[] TypeOptions { get; set; }
@@ -89,17 +107,22 @@ namespace MediaBrowser.Model.Configuration
public class MediaPathInfo
{
public string Path { get; set; }
+
public string NetworkPath { get; set; }
}
public class TypeOptions
{
public string Type { get; set; }
+
public string[] MetadataFetchers { get; set; }
+
public string[] MetadataFetcherOrder { get; set; }
public string[] ImageFetchers { get; set; }
+
public string[] ImageFetcherOrder { get; set; }
+
public ImageOption[] ImageOptions { get; set; }
public ImageOption GetImageOptions(ImageType type)
diff --git a/MediaBrowser.Model/Configuration/MetadataPluginType.cs b/MediaBrowser.Model/Configuration/MetadataPluginType.cs
index bff12799f..4c5e95266 100644
--- a/MediaBrowser.Model/Configuration/MetadataPluginType.cs
+++ b/MediaBrowser.Model/Configuration/MetadataPluginType.cs
@@ -3,7 +3,7 @@
namespace MediaBrowser.Model.Configuration
{
/// <summary>
- /// Enum MetadataPluginType
+ /// Enum MetadataPluginType.
/// </summary>
public enum MetadataPluginType
{
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index afbe02dd3..c66091f9d 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -2,7 +2,9 @@
#pragma warning disable CS1591
using System;
+using System.Collections.Generic;
using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Updates;
namespace MediaBrowser.Model.Configuration
{
@@ -80,8 +82,6 @@ namespace MediaBrowser.Model.Configuration
public bool EnableRemoteAccess { get; set; }
- public bool CollectionsUpgraded { get; set; }
-
/// <summary>
/// Gets or sets a value indicating whether [enable case sensitive item ids].
/// </summary>
@@ -111,19 +111,19 @@ namespace MediaBrowser.Model.Configuration
public string MetadataCountryCode { get; set; }
/// <summary>
- /// Characters to be replaced with a ' ' in strings to create a sort name
+ /// Characters to be replaced with a ' ' in strings to create a sort name.
/// </summary>
/// <value>The sort replace characters.</value>
public string[] SortReplaceCharacters { get; set; }
/// <summary>
- /// Characters to be removed from strings to create a sort name
+ /// Characters to be removed from strings to create a sort name.
/// </summary>
/// <value>The sort remove characters.</value>
public string[] SortRemoveCharacters { get; set; }
/// <summary>
- /// Words to be removed from strings to create a sort name
+ /// Words to be removed from strings to create a sort name.
/// </summary>
/// <value>The sort remove words.</value>
public string[] SortRemoveWords { get; set; }
@@ -229,6 +229,8 @@ namespace MediaBrowser.Model.Configuration
public string[] CodecsUsed { get; set; }
+ public List<RepositoryInfo> PluginRepositories { get; set; }
+
public bool IgnoreVirtualInterfaces { get; set; }
public bool EnableExternalContentInSuggestions { get; set; }
@@ -253,6 +255,16 @@ namespace MediaBrowser.Model.Configuration
public string[] UninstalledPlugins { get; set; }
/// <summary>
+ /// Gets or sets a value indicating whether slow server responses should be logged as a warning.
+ /// </summary>
+ public bool EnableSlowResponseWarning { get; set; }
+
+ /// <summary>
+ /// Gets or sets the threshold for the slow response time warning in ms.
+ /// </summary>
+ public long SlowResponseThresholdMs { get; set; }
+
+ /// <summary>
/// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
/// </summary>
public ServerConfiguration()
@@ -265,6 +277,9 @@ namespace MediaBrowser.Model.Configuration
PathSubstitutions = Array.Empty<PathSubstitution>();
IgnoreVirtualInterfaces = false;
EnableSimpleArtistDetection = false;
+ SkipDeserializationForBasicTypes = true;
+
+ PluginRepositories = new List<RepositoryInfo>();
DisplaySpecialsWithinSeasons = true;
EnableExternalContentInSuggestions = true;
@@ -278,6 +293,9 @@ namespace MediaBrowser.Model.Configuration
EnableHttps = false;
EnableDashboardResponseCaching = true;
EnableCaseSensitiveItemIds = true;
+ EnableNormalizedItemByNameIds = true;
+ DisableLiveTvChannelUserDataName = true;
+ EnableNewOmdbSupport = true;
AutoRunWebApp = true;
EnableRemoteAccess = true;
@@ -351,6 +369,9 @@ namespace MediaBrowser.Model.Configuration
DisabledImageFetchers = new[] { "The Open Movie Database", "TheMovieDb" }
}
};
+
+ EnableSlowResponseWarning = true;
+ SlowResponseThresholdMs = 500;
}
}
diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs
index 85d864eec..cc0e0c468 100644
--- a/MediaBrowser.Model/Configuration/UserConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs
@@ -7,7 +7,7 @@ using Jellyfin.Data.Enums;
namespace MediaBrowser.Model.Configuration
{
/// <summary>
- /// Class UserConfiguration
+ /// Class UserConfiguration.
/// </summary>
public class UserConfiguration
{
@@ -34,6 +34,7 @@ namespace MediaBrowser.Model.Configuration
public string[] GroupedFolders { get; set; }
public SubtitlePlaybackMode SubtitleMode { get; set; }
+
public bool DisplayCollectionsView { get; set; }
public bool EnableLocalPassword { get; set; }
@@ -41,12 +42,15 @@ namespace MediaBrowser.Model.Configuration
public string[] OrderedViews { get; set; }
public string[] LatestItemsExcludes { get; set; }
+
public string[] MyMediaExcludes { get; set; }
public bool HidePlayedInLatest { get; set; }
public bool RememberAudioSelections { get; set; }
+
public bool RememberSubtitleSelections { get; set; }
+
public bool EnableNextEpisodeAutoPlay { get; set; }
/// <summary>
diff --git a/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs b/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs
index c48a38192..4d5f996f8 100644
--- a/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs
+++ b/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs
@@ -10,6 +10,7 @@ namespace MediaBrowser.Model.Configuration
public string ReleaseDateFormat { get; set; }
public bool SaveImagePathsInNfo { get; set; }
+
public bool EnablePathSubstitution { get; set; }
public bool EnableExtraThumbsDuplication { get; set; }
diff --git a/MediaBrowser.Model/Dlna/AudioOptions.cs b/MediaBrowser.Model/Dlna/AudioOptions.cs
index fc555c5f7..67e4ffe03 100644
--- a/MediaBrowser.Model/Dlna/AudioOptions.cs
+++ b/MediaBrowser.Model/Dlna/AudioOptions.cs
@@ -47,7 +47,7 @@ namespace MediaBrowser.Model.Dlna
public int? MaxAudioChannels { get; set; }
/// <summary>
- /// The application's configured quality setting
+ /// The application's configured quality setting.
/// </summary>
public long? MaxBitrate { get; set; }
@@ -85,6 +85,7 @@ namespace MediaBrowser.Model.Dlna
{
return Profile.MaxStaticMusicBitrate;
}
+
return Profile.MaxStaticBitrate;
}
diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
index b055ad41a..a579f8464 100644
--- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
+++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
@@ -79,11 +79,11 @@ namespace MediaBrowser.Model.Dlna
DlnaFlags.InteractiveTransferMode |
DlnaFlags.DlnaV15;
- //if (isDirectStream)
+ // if (isDirectStream)
//{
// flagValue = flagValue | DlnaFlags.ByteBasedSeek;
//}
- //else if (runtimeTicks.HasValue)
+ // else if (runtimeTicks.HasValue)
//{
// flagValue = flagValue | DlnaFlags.TimeBasedSeek;
//}
@@ -148,11 +148,11 @@ namespace MediaBrowser.Model.Dlna
DlnaFlags.InteractiveTransferMode |
DlnaFlags.DlnaV15;
- //if (isDirectStream)
+ // if (isDirectStream)
//{
// flagValue = flagValue | DlnaFlags.ByteBasedSeek;
//}
- //else if (runtimeTicks.HasValue)
+ // else if (runtimeTicks.HasValue)
//{
// flagValue = flagValue | DlnaFlags.TimeBasedSeek;
//}
diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs
index 6462ffdc1..7e921b1fd 100644
--- a/MediaBrowser.Model/Dlna/DeviceProfile.cs
+++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs
@@ -27,16 +27,25 @@ namespace MediaBrowser.Model.Dlna
public DeviceIdentification Identification { get; set; }
public string FriendlyName { get; set; }
+
public string Manufacturer { get; set; }
+
public string ManufacturerUrl { get; set; }
+
public string ModelName { get; set; }
+
public string ModelDescription { get; set; }
+
public string ModelNumber { get; set; }
+
public string ModelUrl { get; set; }
+
public string SerialNumber { get; set; }
public bool EnableAlbumArtInDidl { get; set; }
+
public bool EnableSingleAlbumArtLimit { get; set; }
+
public bool EnableSingleSubtitleLimit { get; set; }
public string SupportedMediaTypes { get; set; }
@@ -46,15 +55,19 @@ namespace MediaBrowser.Model.Dlna
public string AlbumArtPn { get; set; }
public int MaxAlbumArtWidth { get; set; }
+
public int MaxAlbumArtHeight { get; set; }
public int? MaxIconWidth { get; set; }
+
public int? MaxIconHeight { get; set; }
public long? MaxStreamingBitrate { get; set; }
+
public long? MaxStaticBitrate { get; set; }
public int? MusicStreamingTranscodingBitrate { get; set; }
+
public int? MaxStaticMusicBitrate { get; set; }
/// <summary>
@@ -65,10 +78,13 @@ namespace MediaBrowser.Model.Dlna
public string ProtocolInfo { get; set; }
public int TimelineOffsetSeconds { get; set; }
+
public bool RequiresPlainVideoItems { get; set; }
+
public bool RequiresPlainFolders { get; set; }
public bool EnableMSMediaReceiverRegistrar { get; set; }
+
public bool IgnoreTranscodeByteRangeRequests { get; set; }
public XmlAttribute[] XmlRootAttributes { get; set; }
@@ -88,6 +104,7 @@ namespace MediaBrowser.Model.Dlna
public ContainerProfile[] ContainerProfiles { get; set; }
public CodecProfile[] CodecProfiles { get; set; }
+
public ResponseProfile[] ResponseProfiles { get; set; }
public SubtitleProfile[] SubtitleProfiles { get; set; }
@@ -169,6 +186,7 @@ namespace MediaBrowser.Model.Dlna
return i;
}
+
return null;
}
@@ -209,6 +227,7 @@ namespace MediaBrowser.Model.Dlna
return i;
}
+
return null;
}
@@ -254,6 +273,7 @@ namespace MediaBrowser.Model.Dlna
return i;
}
+
return null;
}
@@ -318,6 +338,7 @@ namespace MediaBrowser.Model.Dlna
return i;
}
+
return null;
}
}
diff --git a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs
index 10e9179c0..bdc5f8bb7 100644
--- a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs
+++ b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs
@@ -32,18 +32,25 @@ namespace MediaBrowser.Model.Dlna
}
if (string.Equals(container, "avi", StringComparison.OrdinalIgnoreCase))
+ {
return new MediaFormatProfile[] { MediaFormatProfile.AVI };
+ }
if (string.Equals(container, "mkv", StringComparison.OrdinalIgnoreCase))
+ {
return new MediaFormatProfile[] { MediaFormatProfile.MATROSKA };
+ }
if (string.Equals(container, "mpeg2ps", StringComparison.OrdinalIgnoreCase) ||
string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase))
-
+ {
return new MediaFormatProfile[] { MediaFormatProfile.MPEG_PS_NTSC, MediaFormatProfile.MPEG_PS_PAL };
+ }
if (string.Equals(container, "mpeg1video", StringComparison.OrdinalIgnoreCase))
+ {
return new MediaFormatProfile[] { MediaFormatProfile.MPEG1 };
+ }
if (string.Equals(container, "mpeg2ts", StringComparison.OrdinalIgnoreCase) ||
string.Equals(container, "mpegts", StringComparison.OrdinalIgnoreCase) ||
@@ -54,10 +61,14 @@ namespace MediaBrowser.Model.Dlna
}
if (string.Equals(container, "flv", StringComparison.OrdinalIgnoreCase))
+ {
return new MediaFormatProfile[] { MediaFormatProfile.FLV };
+ }
if (string.Equals(container, "wtv", StringComparison.OrdinalIgnoreCase))
+ {
return new MediaFormatProfile[] { MediaFormatProfile.WTV };
+ }
if (string.Equals(container, "3gp", StringComparison.OrdinalIgnoreCase))
{
@@ -66,7 +77,9 @@ namespace MediaBrowser.Model.Dlna
}
if (string.Equals(container, "ogv", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase))
+ {
return new MediaFormatProfile[] { MediaFormatProfile.OGV };
+ }
return Array.Empty<MediaFormatProfile>();
}
@@ -107,10 +120,13 @@ namespace MediaBrowser.Model.Dlna
return list.ToArray();
}
+
if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
{
if (string.Equals(audioCodec, "lpcm", StringComparison.OrdinalIgnoreCase))
+ {
return new MediaFormatProfile[] { MediaFormatProfile.AVC_TS_HD_50_LPCM_T };
+ }
if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase))
{
@@ -133,14 +149,20 @@ namespace MediaBrowser.Model.Dlna
}
if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
+ {
return new MediaFormatProfile[] { ValueOf(string.Format("AVC_TS_MP_{0}D_AAC_MULT5{1}", resolution, suffix)) };
+ }
if (string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
+ {
return new MediaFormatProfile[] { ValueOf(string.Format("AVC_TS_MP_{0}D_MPEG1_L3{1}", resolution, suffix)) };
+ }
if (string.IsNullOrEmpty(audioCodec) ||
string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase))
+ {
return new MediaFormatProfile[] { ValueOf(string.Format("AVC_TS_MP_{0}D_AC3{1}", resolution, suffix)) };
+ }
}
else if (string.Equals(videoCodec, "vc1", StringComparison.OrdinalIgnoreCase))
{
@@ -150,29 +172,41 @@ namespace MediaBrowser.Model.Dlna
{
return new MediaFormatProfile[] { MediaFormatProfile.VC1_TS_AP_L2_AC3_ISO };
}
+
return new MediaFormatProfile[] { MediaFormatProfile.VC1_TS_AP_L1_AC3_ISO };
}
+
if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase))
{
suffix = string.Equals(suffix, "_ISO", StringComparison.OrdinalIgnoreCase) ? suffix : "_T";
return new MediaFormatProfile[] { ValueOf(string.Format("VC1_TS_HD_DTS{0}", suffix)) };
}
-
}
else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase) || string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
{
if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
+ {
return new MediaFormatProfile[] { ValueOf(string.Format("MPEG4_P2_TS_ASP_AAC{0}", suffix)) };
+ }
+
if (string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
+ {
return new MediaFormatProfile[] { ValueOf(string.Format("MPEG4_P2_TS_ASP_MPEG1_L3{0}", suffix)) };
+ }
+
if (string.Equals(audioCodec, "mp2", StringComparison.OrdinalIgnoreCase))
+ {
return new MediaFormatProfile[] { ValueOf(string.Format("MPEG4_P2_TS_ASP_MPEG2_L2{0}", suffix)) };
+ }
+
if (string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase))
+ {
return new MediaFormatProfile[] { ValueOf(string.Format("MPEG4_P2_TS_ASP_AC3{0}", suffix)) };
+ }
}
- return new MediaFormatProfile[] { };
+ return Array.Empty<MediaFormatProfile>();
}
private MediaFormatProfile ValueOf(string value)
@@ -185,27 +219,36 @@ namespace MediaBrowser.Model.Dlna
if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
{
if (string.Equals(audioCodec, "lpcm", StringComparison.OrdinalIgnoreCase))
+ {
return MediaFormatProfile.AVC_MP4_LPCM;
+ }
+
if (string.IsNullOrEmpty(audioCodec) ||
string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase))
{
return MediaFormatProfile.AVC_MP4_MP_SD_AC3;
}
+
if (string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
{
return MediaFormatProfile.AVC_MP4_MP_SD_MPEG1_L3;
}
+
if (width.HasValue && height.HasValue)
{
if ((width.Value <= 720) && (height.Value <= 576))
{
if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
+ {
return MediaFormatProfile.AVC_MP4_MP_SD_AAC_MULT5;
+ }
}
else if ((width.Value <= 1280) && (height.Value <= 720))
{
if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
+ {
return MediaFormatProfile.AVC_MP4_MP_HD_720p_AAC;
+ }
}
else if ((width.Value <= 1920) && (height.Value <= 1080))
{
@@ -222,7 +265,10 @@ namespace MediaBrowser.Model.Dlna
if (width.HasValue && height.HasValue && width.Value <= 720 && height.Value <= 576)
{
if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
+ {
return MediaFormatProfile.MPEG4_P2_MP4_ASP_AAC;
+ }
+
if (string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase) || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
{
return MediaFormatProfile.MPEG4_P2_MP4_NDSD;
@@ -246,15 +292,22 @@ namespace MediaBrowser.Model.Dlna
if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
+ {
return MediaFormatProfile.AVC_3GPP_BL_QCIF15_AAC;
+ }
}
else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase) ||
string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "wma", StringComparison.OrdinalIgnoreCase))
+ {
return MediaFormatProfile.MPEG4_P2_3GPP_SP_L0B_AAC;
+ }
+
if (string.Equals(audioCodec, "amrnb", StringComparison.OrdinalIgnoreCase))
+ {
return MediaFormatProfile.MPEG4_P2_3GPP_SP_L0B_AMR;
+ }
}
else if (string.Equals(videoCodec, "h263", StringComparison.OrdinalIgnoreCase) && string.Equals(audioCodec, "amrnb", StringComparison.OrdinalIgnoreCase))
{
@@ -278,6 +331,7 @@ namespace MediaBrowser.Model.Dlna
{
return MediaFormatProfile.WMVMED_FULL;
}
+
return MediaFormatProfile.WMVMED_PRO;
}
}
@@ -286,6 +340,7 @@ namespace MediaBrowser.Model.Dlna
{
return MediaFormatProfile.WMVHIGH_FULL;
}
+
return MediaFormatProfile.WMVHIGH_PRO;
}
@@ -294,11 +349,19 @@ namespace MediaBrowser.Model.Dlna
if (width.HasValue && height.HasValue)
{
if ((width.Value <= 720) && (height.Value <= 576))
+ {
return MediaFormatProfile.VC1_ASF_AP_L1_WMA;
+ }
+
if ((width.Value <= 1280) && (height.Value <= 720))
+ {
return MediaFormatProfile.VC1_ASF_AP_L2_WMA;
+ }
+
if ((width.Value <= 1920) && (height.Value <= 1080))
+ {
return MediaFormatProfile.VC1_ASF_AP_L3_WMA;
+ }
}
}
else if (string.Equals(videoCodec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
@@ -312,27 +375,41 @@ namespace MediaBrowser.Model.Dlna
public MediaFormatProfile? ResolveAudioFormat(string container, int? bitrate, int? frequency, int? channels)
{
if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase))
+ {
return ResolveAudioASFFormat(bitrate);
+ }
if (string.Equals(container, "mp3", StringComparison.OrdinalIgnoreCase))
+ {
return MediaFormatProfile.MP3;
+ }
if (string.Equals(container, "lpcm", StringComparison.OrdinalIgnoreCase))
+ {
return ResolveAudioLPCMFormat(frequency, channels);
+ }
if (string.Equals(container, "mp4", StringComparison.OrdinalIgnoreCase) ||
string.Equals(container, "aac", StringComparison.OrdinalIgnoreCase))
+ {
return ResolveAudioMP4Format(bitrate);
+ }
if (string.Equals(container, "adts", StringComparison.OrdinalIgnoreCase))
+ {
return ResolveAudioADTSFormat(bitrate);
+ }
if (string.Equals(container, "flac", StringComparison.OrdinalIgnoreCase))
+ {
return MediaFormatProfile.FLAC;
+ }
if (string.Equals(container, "oga", StringComparison.OrdinalIgnoreCase) ||
string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase))
+ {
return MediaFormatProfile.OGG;
+ }
return null;
}
@@ -343,6 +420,7 @@ namespace MediaBrowser.Model.Dlna
{
return MediaFormatProfile.WMA_BASE;
}
+
return MediaFormatProfile.WMA_FULL;
}
@@ -354,14 +432,17 @@ namespace MediaBrowser.Model.Dlna
{
return MediaFormatProfile.LPCM16_44_MONO;
}
+
if (frequency.Value == 44100 && channels.Value == 2)
{
return MediaFormatProfile.LPCM16_44_STEREO;
}
+
if (frequency.Value == 48000 && channels.Value == 1)
{
return MediaFormatProfile.LPCM16_48_MONO;
}
+
if (frequency.Value == 48000 && channels.Value == 2)
{
return MediaFormatProfile.LPCM16_48_STEREO;
@@ -379,6 +460,7 @@ namespace MediaBrowser.Model.Dlna
{
return MediaFormatProfile.AAC_ISO_320;
}
+
return MediaFormatProfile.AAC_ISO;
}
@@ -388,6 +470,7 @@ namespace MediaBrowser.Model.Dlna
{
return MediaFormatProfile.AAC_ADTS_320;
}
+
return MediaFormatProfile.AAC_ADTS;
}
@@ -398,13 +481,19 @@ namespace MediaBrowser.Model.Dlna
return ResolveImageJPGFormat(width, height);
if (string.Equals(container, "png", StringComparison.OrdinalIgnoreCase))
+ {
return ResolveImagePNGFormat(width, height);
+ }
if (string.Equals(container, "gif", StringComparison.OrdinalIgnoreCase))
+ {
return MediaFormatProfile.GIF_LRG;
+ }
if (string.Equals(container, "raw", StringComparison.OrdinalIgnoreCase))
+ {
return MediaFormatProfile.RAW;
+ }
return null;
}
@@ -414,10 +503,14 @@ namespace MediaBrowser.Model.Dlna
if (width.HasValue && height.HasValue)
{
if ((width.Value <= 160) && (height.Value <= 160))
+ {
return MediaFormatProfile.JPEG_TN;
+ }
if ((width.Value <= 640) && (height.Value <= 480))
+ {
return MediaFormatProfile.JPEG_SM;
+ }
if ((width.Value <= 1024) && (height.Value <= 768))
{
@@ -435,7 +528,9 @@ namespace MediaBrowser.Model.Dlna
if (width.HasValue && height.HasValue)
{
if ((width.Value <= 160) && (height.Value <= 160))
+ {
return MediaFormatProfile.PNG_TN;
+ }
}
return MediaFormatProfile.PNG_LRG;
diff --git a/MediaBrowser.Model/Dlna/ProfileCondition.cs b/MediaBrowser.Model/Dlna/ProfileCondition.cs
index f8b5dee81..4b39d6875 100644
--- a/MediaBrowser.Model/Dlna/ProfileCondition.cs
+++ b/MediaBrowser.Model/Dlna/ProfileCondition.cs
@@ -15,7 +15,6 @@ namespace MediaBrowser.Model.Dlna
public ProfileCondition(ProfileConditionType condition, ProfileConditionValue property, string value)
: this(condition, property, value, false)
{
-
}
public ProfileCondition(ProfileConditionType condition, ProfileConditionValue property, string value, bool isRequired)
diff --git a/MediaBrowser.Model/Dlna/SortCriteria.cs b/MediaBrowser.Model/Dlna/SortCriteria.cs
index 3f8985fdc..1f7fa76ad 100644
--- a/MediaBrowser.Model/Dlna/SortCriteria.cs
+++ b/MediaBrowser.Model/Dlna/SortCriteria.cs
@@ -10,7 +10,6 @@ namespace MediaBrowser.Model.Dlna
public SortCriteria(string value)
{
-
}
}
}
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index a18ad36c5..cfe862f5a 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -109,7 +109,6 @@ namespace MediaBrowser.Model.Dlna
}
return 1;
-
}).ThenBy(i =>
{
switch (i.PlayMethod)
@@ -121,7 +120,6 @@ namespace MediaBrowser.Model.Dlna
default:
return 1;
}
-
}).ThenBy(i =>
{
switch (i.MediaSource.Protocol)
@@ -131,7 +129,6 @@ namespace MediaBrowser.Model.Dlna
default:
return 1;
}
-
}).ThenBy(i =>
{
if (maxBitrate > 0)
@@ -143,7 +140,6 @@ namespace MediaBrowser.Model.Dlna
}
return 0;
-
}).ThenBy(streams.IndexOf);
}
@@ -388,7 +384,10 @@ namespace MediaBrowser.Model.Dlna
audioCodecProfiles.Add(i);
}
- if (audioCodecProfiles.Count >= 1) break;
+ if (audioCodecProfiles.Count >= 1)
+ {
+ break;
+ }
}
var audioTranscodingConditions = new List<ProfileCondition>();
@@ -631,10 +630,12 @@ namespace MediaBrowser.Model.Dlna
{
playlistItem.MinSegments = transcodingProfile.MinSegments;
}
+
if (transcodingProfile.SegmentLength > 0)
{
playlistItem.SegmentLength = transcodingProfile.SegmentLength;
}
+
playlistItem.SubProtocol = transcodingProfile.Protocol;
if (!string.IsNullOrEmpty(transcodingProfile.MaxAudioChannels)
@@ -781,7 +782,7 @@ namespace MediaBrowser.Model.Dlna
if (!ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
{
- //LogConditionFailure(options.Profile, "VideoCodecProfile.ApplyConditions", applyCondition, item);
+ // LogConditionFailure(options.Profile, "VideoCodecProfile.ApplyConditions", applyCondition, item);
applyConditions = false;
break;
}
@@ -825,7 +826,7 @@ namespace MediaBrowser.Model.Dlna
if (!ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio))
{
- //LogConditionFailure(options.Profile, "VideoCodecProfile.ApplyConditions", applyCondition, item);
+ // LogConditionFailure(options.Profile, "VideoCodecProfile.ApplyConditions", applyCondition, item);
applyConditions = false;
break;
}
@@ -951,6 +952,7 @@ namespace MediaBrowser.Model.Dlna
{
return (PlayMethod.DirectPlay, new List<TranscodeReason>());
}
+
if (options.ForceDirectStream)
{
return (PlayMethod.DirectStream, new List<TranscodeReason>());
@@ -1046,7 +1048,7 @@ namespace MediaBrowser.Model.Dlna
{
if (!ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
{
- //LogConditionFailure(profile, "VideoCodecProfile.ApplyConditions", applyCondition, mediaSource);
+ // LogConditionFailure(profile, "VideoCodecProfile.ApplyConditions", applyCondition, mediaSource);
applyConditions = false;
break;
}
@@ -1092,7 +1094,7 @@ namespace MediaBrowser.Model.Dlna
{
if (!ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio))
{
- //LogConditionFailure(profile, "VideoAudioCodecProfile.ApplyConditions", applyCondition, mediaSource);
+ // LogConditionFailure(profile, "VideoAudioCodecProfile.ApplyConditions", applyCondition, mediaSource);
applyConditions = false;
break;
}
@@ -1265,6 +1267,7 @@ namespace MediaBrowser.Model.Dlna
return true;
}
}
+
return false;
}
@@ -1367,14 +1370,17 @@ namespace MediaBrowser.Model.Dlna
{
throw new ArgumentException("ItemId is required");
}
+
if (string.IsNullOrEmpty(options.DeviceId))
{
throw new ArgumentException("DeviceId is required");
}
+
if (options.Profile == null)
{
throw new ArgumentException("Profile is required");
}
+
if (options.MediaSources == null)
{
throw new ArgumentException("MediaSources is required");
@@ -1422,6 +1428,7 @@ namespace MediaBrowser.Model.Dlna
item.AudioBitrate = Math.Max(num, item.AudioBitrate ?? num);
}
}
+
break;
}
case ProfileConditionValue.AudioChannels:
@@ -1456,6 +1463,7 @@ namespace MediaBrowser.Model.Dlna
item.SetOption(qualifier, "audiochannels", Math.Max(num, item.GetTargetAudioChannels(qualifier) ?? num).ToString(CultureInfo.InvariantCulture));
}
}
+
break;
}
case ProfileConditionValue.IsAvc:
@@ -1476,6 +1484,7 @@ namespace MediaBrowser.Model.Dlna
item.RequireAvc = true;
}
}
+
break;
}
case ProfileConditionValue.IsAnamorphic:
@@ -1496,6 +1505,7 @@ namespace MediaBrowser.Model.Dlna
item.RequireNonAnamorphic = true;
}
}
+
break;
}
case ProfileConditionValue.IsInterlaced:
@@ -1526,6 +1536,7 @@ namespace MediaBrowser.Model.Dlna
item.SetOption(qualifier, "deinterlace", "true");
}
}
+
break;
}
case ProfileConditionValue.AudioProfile:
@@ -1571,6 +1582,7 @@ namespace MediaBrowser.Model.Dlna
item.SetOption(qualifier, "maxrefframes", Math.Max(num, item.GetTargetRefFrames(qualifier) ?? num).ToString(CultureInfo.InvariantCulture));
}
}
+
break;
}
case ProfileConditionValue.VideoBitDepth:
@@ -1605,6 +1617,7 @@ namespace MediaBrowser.Model.Dlna
item.SetOption(qualifier, "videobitdepth", Math.Max(num, item.GetTargetVideoBitDepth(qualifier) ?? num).ToString(CultureInfo.InvariantCulture));
}
}
+
break;
}
case ProfileConditionValue.VideoProfile:
@@ -1627,6 +1640,7 @@ namespace MediaBrowser.Model.Dlna
item.SetOption(qualifier, "profile", string.Join(",", values));
}
}
+
break;
}
case ProfileConditionValue.Height:
@@ -1651,6 +1665,7 @@ namespace MediaBrowser.Model.Dlna
item.MaxHeight = Math.Max(num, item.MaxHeight ?? num);
}
}
+
break;
}
case ProfileConditionValue.VideoBitrate:
@@ -1675,6 +1690,7 @@ namespace MediaBrowser.Model.Dlna
item.VideoBitrate = Math.Max(num, item.VideoBitrate ?? num);
}
}
+
break;
}
case ProfileConditionValue.VideoFramerate:
@@ -1699,6 +1715,7 @@ namespace MediaBrowser.Model.Dlna
item.MaxFramerate = Math.Max(num, item.MaxFramerate ?? num);
}
}
+
break;
}
case ProfileConditionValue.VideoLevel:
@@ -1723,6 +1740,7 @@ namespace MediaBrowser.Model.Dlna
item.SetOption(qualifier, "level", Math.Max(num, item.GetTargetVideoLevel(qualifier) ?? num).ToString(CultureInfo.InvariantCulture));
}
}
+
break;
}
case ProfileConditionValue.Width:
@@ -1747,8 +1765,10 @@ namespace MediaBrowser.Model.Dlna
item.MaxWidth = Math.Max(num, item.MaxWidth ?? num);
}
}
+
break;
}
+
default:
break;
}
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index 244463803..204340c46 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -69,6 +69,7 @@ namespace MediaBrowser.Model.Dlna
public Guid ItemId { get; set; }
public PlayMethod PlayMethod { get; set; }
+
public EncodingContext Context { get; set; }
public DlnaProfileType MediaType { get; set; }
@@ -80,15 +81,23 @@ namespace MediaBrowser.Model.Dlna
public long StartPositionTicks { get; set; }
public int? SegmentLength { get; set; }
+
public int? MinSegments { get; set; }
+
public bool BreakOnNonKeyFrames { get; set; }
public bool RequireAvc { get; set; }
+
public bool RequireNonAnamorphic { get; set; }
+
public bool CopyTimestamps { get; set; }
+
public bool EnableMpegtsM2TsMode { get; set; }
+
public bool EnableSubtitlesInManifest { get; set; }
+
public string[] AudioCodecs { get; set; }
+
public string[] VideoCodecs { get; set; }
public int? AudioStreamIndex { get; set; }
@@ -96,6 +105,7 @@ namespace MediaBrowser.Model.Dlna
public int? SubtitleStreamIndex { get; set; }
public int? TranscodingMaxAudioChannels { get; set; }
+
public int? GlobalMaxAudioChannels { get; set; }
public int? AudioBitrate { get; set; }
@@ -103,12 +113,15 @@ namespace MediaBrowser.Model.Dlna
public int? VideoBitrate { get; set; }
public int? MaxWidth { get; set; }
+
public int? MaxHeight { get; set; }
public float? MaxFramerate { get; set; }
public DeviceProfile DeviceProfile { get; set; }
+
public string DeviceProfileId { get; set; }
+
public string DeviceId { get; set; }
public long? RunTimeTicks { get; set; }
@@ -120,15 +133,18 @@ namespace MediaBrowser.Model.Dlna
public MediaSourceInfo MediaSource { get; set; }
public string[] SubtitleCodecs { get; set; }
+
public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
+
public string SubtitleFormat { get; set; }
public string PlaySessionId { get; set; }
+
public TranscodeReason[] TranscodeReasons { get; set; }
public Dictionary<string, string> StreamOptions { get; private set; }
- public string MediaSourceId => MediaSource == null ? null : MediaSource.Id;
+ public string MediaSourceId => MediaSource?.Id;
public bool IsDirectStream =>
PlayMethod == PlayMethod.DirectStream ||
@@ -160,11 +176,13 @@ namespace MediaBrowser.Model.Dlna
{
continue;
}
+
if (string.Equals(pair.Name, "SubtitleStreamIndex", StringComparison.OrdinalIgnoreCase) &&
string.Equals(pair.Value, "-1", StringComparison.OrdinalIgnoreCase))
{
continue;
}
+
if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase) &&
string.Equals(pair.Value, "false", StringComparison.OrdinalIgnoreCase))
{
@@ -465,7 +483,7 @@ namespace MediaBrowser.Model.Dlna
}
/// <summary>
- /// Returns the audio stream that will be used
+ /// Returns the audio stream that will be used.
/// </summary>
public MediaStream TargetAudioStream
{
@@ -481,7 +499,7 @@ namespace MediaBrowser.Model.Dlna
}
/// <summary>
- /// Returns the video stream that will be used
+ /// Returns the video stream that will be used.
/// </summary>
public MediaStream TargetVideoStream
{
@@ -497,7 +515,7 @@ namespace MediaBrowser.Model.Dlna
}
/// <summary>
- /// Predicts the audio sample rate that will be in the output stream
+ /// Predicts the audio sample rate that will be in the output stream.
/// </summary>
public int? TargetAudioSampleRate
{
@@ -509,7 +527,7 @@ namespace MediaBrowser.Model.Dlna
}
/// <summary>
- /// Predicts the audio sample rate that will be in the output stream
+ /// Predicts the audio sample rate that will be in the output stream.
/// </summary>
public int? TargetAudioBitDepth
{
@@ -532,7 +550,7 @@ namespace MediaBrowser.Model.Dlna
}
/// <summary>
- /// Predicts the audio sample rate that will be in the output stream
+ /// Predicts the audio sample rate that will be in the output stream.
/// </summary>
public int? TargetVideoBitDepth
{
@@ -579,7 +597,7 @@ namespace MediaBrowser.Model.Dlna
}
/// <summary>
- /// Predicts the audio sample rate that will be in the output stream
+ /// Predicts the audio sample rate that will be in the output stream.
/// </summary>
public float? TargetFramerate
{
@@ -593,7 +611,7 @@ namespace MediaBrowser.Model.Dlna
}
/// <summary>
- /// Predicts the audio sample rate that will be in the output stream
+ /// Predicts the audio sample rate that will be in the output stream.
/// </summary>
public double? TargetVideoLevel
{
@@ -680,7 +698,7 @@ namespace MediaBrowser.Model.Dlna
}
/// <summary>
- /// Predicts the audio sample rate that will be in the output stream
+ /// Predicts the audio sample rate that will be in the output stream.
/// </summary>
public int? TargetPacketLength
{
@@ -694,7 +712,7 @@ namespace MediaBrowser.Model.Dlna
}
/// <summary>
- /// Predicts the audio sample rate that will be in the output stream
+ /// Predicts the audio sample rate that will be in the output stream.
/// </summary>
public string TargetVideoProfile
{
@@ -732,7 +750,7 @@ namespace MediaBrowser.Model.Dlna
}
/// <summary>
- /// Predicts the audio bitrate that will be in the output stream
+ /// Predicts the audio bitrate that will be in the output stream.
/// </summary>
public int? TargetAudioBitrate
{
@@ -746,7 +764,7 @@ namespace MediaBrowser.Model.Dlna
}
/// <summary>
- /// Predicts the audio channels that will be in the output stream
+ /// Predicts the audio channels that will be in the output stream.
/// </summary>
public int? TargetAudioChannels
{
@@ -787,7 +805,7 @@ namespace MediaBrowser.Model.Dlna
}
/// <summary>
- /// Predicts the audio codec that will be in the output stream
+ /// Predicts the audio codec that will be in the output stream.
/// </summary>
public string[] TargetAudioCodec
{
@@ -795,18 +813,18 @@ namespace MediaBrowser.Model.Dlna
{
var stream = TargetAudioStream;
- string inputCodec = stream == null ? null : stream.Codec;
+ string inputCodec = stream?.Codec;
if (IsDirectStream)
{
- return string.IsNullOrEmpty(inputCodec) ? new string[] { } : new[] { inputCodec };
+ return string.IsNullOrEmpty(inputCodec) ? Array.Empty<string>() : new[] { inputCodec };
}
foreach (string codec in AudioCodecs)
{
if (string.Equals(codec, inputCodec, StringComparison.OrdinalIgnoreCase))
{
- return string.IsNullOrEmpty(codec) ? new string[] { } : new[] { codec };
+ return string.IsNullOrEmpty(codec) ? Array.Empty<string>() : new[] { codec };
}
}
@@ -820,18 +838,18 @@ namespace MediaBrowser.Model.Dlna
{
var stream = TargetVideoStream;
- string inputCodec = stream == null ? null : stream.Codec;
+ string inputCodec = stream?.Codec;
if (IsDirectStream)
{
- return string.IsNullOrEmpty(inputCodec) ? new string[] { } : new[] { inputCodec };
+ return string.IsNullOrEmpty(inputCodec) ? Array.Empty<string>() : new[] { inputCodec };
}
foreach (string codec in VideoCodecs)
{
if (string.Equals(codec, inputCodec, StringComparison.OrdinalIgnoreCase))
{
- return string.IsNullOrEmpty(codec) ? new string[] { } : new[] { codec };
+ return string.IsNullOrEmpty(codec) ? Array.Empty<string>() : new[] { codec };
}
}
@@ -840,7 +858,7 @@ namespace MediaBrowser.Model.Dlna
}
/// <summary>
- /// Predicts the audio channels that will be in the output stream
+ /// Predicts the audio channels that will be in the output stream.
/// </summary>
public long? TargetSize
{
@@ -993,6 +1011,7 @@ namespace MediaBrowser.Model.Dlna
{
return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
}
+
return GetMediaStreamCount(MediaStreamType.Video, 1);
}
}
@@ -1005,6 +1024,7 @@ namespace MediaBrowser.Model.Dlna
{
return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
}
+
return GetMediaStreamCount(MediaStreamType.Audio, 1);
}
}
diff --git a/MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs b/MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs
index 7b0204590..e7fe8d6af 100644
--- a/MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs
+++ b/MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs
@@ -5,22 +5,22 @@ namespace MediaBrowser.Model.Dlna
public enum SubtitleDeliveryMethod
{
/// <summary>
- /// The encode
+ /// The encode.
/// </summary>
Encode = 0,
/// <summary>
- /// The embed
+ /// The embed.
/// </summary>
Embed = 1,
/// <summary>
- /// The external
+ /// The external.
/// </summary>
External = 2,
/// <summary>
- /// The HLS
+ /// The HLS.
/// </summary>
Hls = 3
}
diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs
index c7f8f0584..af3d83ade 100644
--- a/MediaBrowser.Model/Dto/BaseItemDto.cs
+++ b/MediaBrowser.Model/Dto/BaseItemDto.cs
@@ -62,17 +62,23 @@ namespace MediaBrowser.Model.Dto
public DateTime? DateCreated { get; set; }
public DateTime? DateLastMediaAdded { get; set; }
+
public string ExtraType { get; set; }
public int? AirsBeforeSeasonNumber { get; set; }
+
public int? AirsAfterSeasonNumber { get; set; }
+
public int? AirsBeforeEpisodeNumber { get; set; }
+
public bool? CanDelete { get; set; }
+
public bool? CanDownload { get; set; }
public bool? HasSubtitles { get; set; }
public string PreferredMetadataLanguage { get; set; }
+
public string PreferredMetadataCountryCode { get; set; }
/// <summary>
@@ -87,6 +93,7 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The name of the sort.</value>
public string SortName { get; set; }
+
public string ForcedSortName { get; set; }
/// <summary>
@@ -146,6 +153,7 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The channel identifier.</value>
public Guid ChannelId { get; set; }
+
public string ChannelName { get; set; }
/// <summary>
@@ -213,6 +221,7 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The number.</value>
public string Number { get; set; }
+
public string ChannelNumber { get; set; }
/// <summary>
@@ -308,7 +317,7 @@ namespace MediaBrowser.Model.Dto
public int? LocalTrailerCount { get; set; }
/// <summary>
- /// User data for this item based on the user it's being requested for
+ /// User data for this item based on the user it's being requested for.
/// </summary>
/// <value>The user data.</value>
public UserItemDataDto UserData { get; set; }
@@ -467,6 +476,7 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The part count.</value>
public int? PartCount { get; set; }
+
public int? MediaSourceCount { get; set; }
/// <summary>
@@ -599,6 +609,7 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The series count.</value>
public int? SeriesCount { get; set; }
+
public int? ProgramCount { get; set; }
/// <summary>
/// Gets or sets the episode count.
@@ -615,6 +626,7 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The album count.</value>
public int? AlbumCount { get; set; }
+
public int? ArtistCount { get; set; }
/// <summary>
/// Gets or sets the music video count.
@@ -629,18 +641,31 @@ namespace MediaBrowser.Model.Dto
public bool? LockData { get; set; }
public int? Width { get; set; }
+
public int? Height { get; set; }
+
public string CameraMake { get; set; }
+
public string CameraModel { get; set; }
+
public string Software { get; set; }
+
public double? ExposureTime { get; set; }
+
public double? FocalLength { get; set; }
+
public ImageOrientation? ImageOrientation { get; set; }
+
public double? Aperture { get; set; }
+
public double? ShutterSpeed { get; set; }
+
public double? Latitude { get; set; }
+
public double? Longitude { get; set; }
+
public double? Altitude { get; set; }
+
public int? IsoSpeedRating { get; set; }
/// <summary>
diff --git a/MediaBrowser.Model/Dto/BaseItemPerson.cs b/MediaBrowser.Model/Dto/BaseItemPerson.cs
index b080f3e4a..ddd7667ef 100644
--- a/MediaBrowser.Model/Dto/BaseItemPerson.cs
+++ b/MediaBrowser.Model/Dto/BaseItemPerson.cs
@@ -1,5 +1,7 @@
#nullable disable
+using System.Collections.Generic;
using System.Text.Json.Serialization;
+using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.Dto
{
@@ -39,6 +41,12 @@ namespace MediaBrowser.Model.Dto
public string PrimaryImageTag { get; set; }
/// <summary>
+ /// Gets or sets the primary image blurhash.
+ /// </summary>
+ /// <value>The primary image blurhash.</value>
+ public Dictionary<ImageType, Dictionary<string, string>> ImageBlurHashes { get; set; }
+
+ /// <summary>
/// Gets a value indicating whether this instance has primary image.
/// </summary>
/// <value><c>true</c> if this instance has primary image; otherwise, <c>false</c>.</value>
diff --git a/MediaBrowser.Model/Dto/ImageOptions.cs b/MediaBrowser.Model/Dto/ImageOptions.cs
index 158e622a8..3f4405f1e 100644
--- a/MediaBrowser.Model/Dto/ImageOptions.cs
+++ b/MediaBrowser.Model/Dto/ImageOptions.cs
@@ -61,7 +61,7 @@ namespace MediaBrowser.Model.Dto
/// <summary>
/// Gets or sets the image tag.
- /// If set this will result in strong, unconditional response caching
+ /// If set this will result in strong, unconditional response caching.
/// </summary>
/// <value>The hash.</value>
public string Tag { get; set; }
diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
index 74c2cb4f4..be682be23 100644
--- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs
+++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
@@ -13,39 +13,56 @@ namespace MediaBrowser.Model.Dto
public class MediaSourceInfo
{
public MediaProtocol Protocol { get; set; }
+
public string Id { get; set; }
public string Path { get; set; }
public string EncoderPath { get; set; }
+
public MediaProtocol? EncoderProtocol { get; set; }
public MediaSourceType Type { get; set; }
public string Container { get; set; }
+
public long? Size { get; set; }
public string Name { get; set; }
/// <summary>
- /// Differentiate internet url vs local network
+ /// Differentiate internet url vs local network.
/// </summary>
public bool IsRemote { get; set; }
public string ETag { get; set; }
+
public long? RunTimeTicks { get; set; }
+
public bool ReadAtNativeFramerate { get; set; }
+
public bool IgnoreDts { get; set; }
+
public bool IgnoreIndex { get; set; }
+
public bool GenPtsInput { get; set; }
+
public bool SupportsTranscoding { get; set; }
+
public bool SupportsDirectStream { get; set; }
+
public bool SupportsDirectPlay { get; set; }
+
public bool IsInfiniteStream { get; set; }
+
public bool RequiresOpening { get; set; }
+
public string OpenToken { get; set; }
+
public bool RequiresClosing { get; set; }
+
public string LiveStreamId { get; set; }
+
public int? BufferMs { get; set; }
public bool RequiresLooping { get; set; }
@@ -67,10 +84,13 @@ namespace MediaBrowser.Model.Dto
public int? Bitrate { get; set; }
public TransportStreamTimestamp? Timestamp { get; set; }
+
public Dictionary<string, string> RequiredHttpHeaders { get; set; }
public string TranscodingUrl { get; set; }
+
public string TranscodingSubProtocol { get; set; }
+
public string TranscodingContainer { get; set; }
public int? AnalyzeDurationMs { get; set; }
@@ -118,6 +138,7 @@ namespace MediaBrowser.Model.Dto
public TranscodeReason[] TranscodeReasons { get; set; }
public int? DefaultAudioStreamIndex { get; set; }
+
public int? DefaultSubtitleStreamIndex { get; set; }
public MediaStream GetDefaultAudioStream(int? defaultIndex)
diff --git a/MediaBrowser.Model/Dto/MetadataEditorInfo.cs b/MediaBrowser.Model/Dto/MetadataEditorInfo.cs
index 1d840a300..e4f38d6af 100644
--- a/MediaBrowser.Model/Dto/MetadataEditorInfo.cs
+++ b/MediaBrowser.Model/Dto/MetadataEditorInfo.cs
@@ -11,11 +11,15 @@ namespace MediaBrowser.Model.Dto
public class MetadataEditorInfo
{
public ParentalRating[] ParentalRatingOptions { get; set; }
+
public CountryInfo[] Countries { get; set; }
+
public CultureDto[] Cultures { get; set; }
+
public ExternalIdInfo[] ExternalIdInfos { get; set; }
public string ContentType { get; set; }
+
public NameValuePair[] ContentTypeOptions { get; set; }
public MetadataEditorInfo()
diff --git a/MediaBrowser.Model/Dto/NameIdPair.cs b/MediaBrowser.Model/Dto/NameIdPair.cs
index efb2c157c..45c2fb35d 100644
--- a/MediaBrowser.Model/Dto/NameIdPair.cs
+++ b/MediaBrowser.Model/Dto/NameIdPair.cs
@@ -23,6 +23,7 @@ namespace MediaBrowser.Model.Dto
public class NameGuidPair
{
public string Name { get; set; }
+
public Guid Id { get; set; }
}
}
diff --git a/MediaBrowser.Model/Entities/DisplayPreferences.cs b/MediaBrowser.Model/Entities/DisplayPreferences.cs
index 0e5db01dd..7e5c5be3b 100644
--- a/MediaBrowser.Model/Entities/DisplayPreferences.cs
+++ b/MediaBrowser.Model/Entities/DisplayPreferences.cs
@@ -104,7 +104,7 @@ namespace MediaBrowser.Model.Entities
public bool ShowSidebar { get; set; }
/// <summary>
- /// Gets or sets the client
+ /// Gets or sets the client.
/// </summary>
public string Client { get; set; }
}
diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs
index fa09cc513..7a488005e 100644
--- a/MediaBrowser.Model/Entities/MediaStream.cs
+++ b/MediaBrowser.Model/Entities/MediaStream.cs
@@ -13,7 +13,7 @@ using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.Model.Entities
{
/// <summary>
- /// Class MediaStream
+ /// Class MediaStream.
/// </summary>
public class MediaStream
{
@@ -114,7 +114,7 @@ namespace MediaBrowser.Model.Entities
{
if (Type == MediaStreamType.Audio)
{
- //if (!string.IsNullOrEmpty(Title))
+ // if (!string.IsNullOrEmpty(Title))
//{
// return AddLanguageIfNeeded(Title);
//}
@@ -125,6 +125,7 @@ namespace MediaBrowser.Model.Entities
{
attributes.Add(StringHelper.FirstToUpper(Language));
}
+
if (!string.IsNullOrEmpty(Codec) && !string.Equals(Codec, "dca", StringComparison.OrdinalIgnoreCase))
{
attributes.Add(AudioCodec.GetFriendlyName(Codec));
@@ -142,6 +143,7 @@ namespace MediaBrowser.Model.Entities
{
attributes.Add(Channels.Value.ToString(CultureInfo.InvariantCulture) + " ch");
}
+
if (IsDefault)
{
attributes.Add("Default");
@@ -208,7 +210,6 @@ namespace MediaBrowser.Model.Entities
if (Type == MediaStreamType.Video)
{
-
}
return null;
@@ -228,30 +229,37 @@ namespace MediaBrowser.Model.Entities
{
return "4K";
}
+
if (width >= 2500)
{
if (i.IsInterlaced)
{
return "1440i";
}
+
return "1440p";
}
+
if (width >= 1900 || height >= 1000)
{
if (i.IsInterlaced)
{
return "1080i";
}
+
return "1080p";
}
+
if (width >= 1260 || height >= 700)
{
if (i.IsInterlaced)
{
return "720i";
}
+
return "720p";
}
+
if (width >= 700 || height >= 440)
{
@@ -259,11 +267,13 @@ namespace MediaBrowser.Model.Entities
{
return "480i";
}
+
return "480p";
}
return "SD";
}
+
return null;
}
@@ -411,7 +421,10 @@ namespace MediaBrowser.Model.Entities
{
get
{
- if (Type != MediaStreamType.Subtitle) return false;
+ if (Type != MediaStreamType.Subtitle)
+ {
+ return false;
+ }
if (string.IsNullOrEmpty(Codec) && !IsExternal)
{
@@ -449,6 +462,7 @@ namespace MediaBrowser.Model.Entities
{
return false;
}
+
if (string.Equals(fromCodec, "ssa", StringComparison.OrdinalIgnoreCase))
{
return false;
@@ -459,6 +473,7 @@ namespace MediaBrowser.Model.Entities
{
return false;
}
+
if (string.Equals(toCodec, "ssa", StringComparison.OrdinalIgnoreCase))
{
return false;
diff --git a/MediaBrowser.Model/Entities/MediaUrl.cs b/MediaBrowser.Model/Entities/MediaUrl.cs
index 74f982437..80ceaa765 100644
--- a/MediaBrowser.Model/Entities/MediaUrl.cs
+++ b/MediaBrowser.Model/Entities/MediaUrl.cs
@@ -6,6 +6,7 @@ namespace MediaBrowser.Model.Entities
public class MediaUrl
{
public string Url { get; set; }
+
public string Name { get; set; }
}
}
diff --git a/MediaBrowser.Model/Entities/MetadataProvider.cs b/MediaBrowser.Model/Entities/MetadataProvider.cs
index bcc2b48e7..7fecf67b8 100644
--- a/MediaBrowser.Model/Entities/MetadataProvider.cs
+++ b/MediaBrowser.Model/Entities/MetadataProvider.cs
@@ -3,28 +3,28 @@
namespace MediaBrowser.Model.Entities
{
/// <summary>
- /// Enum MetadataProviders
+ /// Enum MetadataProviders.
/// </summary>
public enum MetadataProvider
{
/// <summary>
- /// The imdb
+ /// The imdb.
/// </summary>
Imdb = 2,
/// <summary>
- /// The TMDB
+ /// The TMDB.
/// </summary>
Tmdb = 3,
/// <summary>
- /// The TVDB
+ /// The TVDB.
/// </summary>
Tvdb = 4,
/// <summary>
- /// The tvcom
+ /// The tvcom.
/// </summary>
Tvcom = 5,
/// <summary>
- /// Tmdb Collection Id
+ /// Tmdb Collection Id.
/// </summary>
TmdbCollection = 7,
MusicBrainzAlbum = 8,
diff --git a/MediaBrowser.Model/Entities/PackageReviewInfo.cs b/MediaBrowser.Model/Entities/PackageReviewInfo.cs
index 1ebbc3323..5b22b34ac 100644
--- a/MediaBrowser.Model/Entities/PackageReviewInfo.cs
+++ b/MediaBrowser.Model/Entities/PackageReviewInfo.cs
@@ -36,6 +36,5 @@ namespace MediaBrowser.Model.Entities
/// Gets or sets the time of review.
/// </summary>
public DateTime timestamp { get; set; }
-
}
}
diff --git a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs
index 2de02e403..f2bc6f25e 100644
--- a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs
+++ b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs
@@ -7,7 +7,7 @@ using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Model.Entities
{
/// <summary>
- /// Used to hold information about a user's list of configured virtual folders
+ /// Used to hold information about a user's list of configured virtual folders.
/// </summary>
public class VirtualFolderInfo
{
@@ -52,6 +52,7 @@ namespace MediaBrowser.Model.Entities
public string PrimaryImageItemId { get; set; }
public double? RefreshProgress { get; set; }
+
public string RefreshStatus { get; set; }
}
}
diff --git a/MediaBrowser.Model/IO/IZipClient.cs b/MediaBrowser.Model/IO/IZipClient.cs
index 83e8a018d..2daa54f22 100644
--- a/MediaBrowser.Model/IO/IZipClient.cs
+++ b/MediaBrowser.Model/IO/IZipClient.cs
@@ -5,7 +5,7 @@ using System.IO;
namespace MediaBrowser.Model.IO
{
/// <summary>
- /// Interface IZipClient
+ /// Interface IZipClient.
/// </summary>
public interface IZipClient
{
diff --git a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs
index 45970cf6b..07e76d960 100644
--- a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs
@@ -124,6 +124,7 @@ namespace MediaBrowser.Model.LiveTv
/// </summary>
/// <value><c>true</c> if this instance is post padding required; otherwise, <c>false</c>.</value>
public bool IsPostPaddingRequired { get; set; }
+
public KeepUntil KeepUntil { get; set; }
}
}
diff --git a/MediaBrowser.Model/LiveTv/ChannelType.cs b/MediaBrowser.Model/LiveTv/ChannelType.cs
index b6974cb08..f4c55cb6d 100644
--- a/MediaBrowser.Model/LiveTv/ChannelType.cs
+++ b/MediaBrowser.Model/LiveTv/ChannelType.cs
@@ -6,7 +6,7 @@ namespace MediaBrowser.Model.LiveTv
public enum ChannelType
{
/// <summary>
- /// The TV
+ /// The TV.
/// </summary>
TV,
diff --git a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs
index d1a94d8b3..2b2377fda 100644
--- a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs
+++ b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs
@@ -54,7 +54,7 @@ namespace MediaBrowser.Model.LiveTv
public int? StartIndex { get; set; }
/// <summary>
- /// The maximum number of items to return
+ /// The maximum number of items to return.
/// </summary>
/// <value>The limit.</value>
public int? Limit { get; set; }
@@ -64,16 +64,17 @@ namespace MediaBrowser.Model.LiveTv
/// </summary>
/// <value><c>true</c> if [add current program]; otherwise, <c>false</c>.</value>
public bool AddCurrentProgram { get; set; }
+
public bool EnableUserData { get; set; }
/// <summary>
- /// Used to specific whether to return news or not
+ /// Used to specific whether to return news or not.
/// </summary>
/// <remarks>If set to null, all programs will be returned</remarks>
public bool? IsNews { get; set; }
/// <summary>
- /// Used to specific whether to return movies or not
+ /// Used to specific whether to return movies or not.
/// </summary>
/// <remarks>If set to null, all programs will be returned</remarks>
public bool? IsMovie { get; set; }
@@ -88,12 +89,13 @@ namespace MediaBrowser.Model.LiveTv
/// </summary>
/// <value><c>null</c> if [is sports] contains no value, <c>true</c> if [is sports]; otherwise, <c>false</c>.</value>
public bool? IsSports { get; set; }
+
public bool? IsSeries { get; set; }
public string[] SortBy { get; set; }
/// <summary>
- /// The sort order to return results with
+ /// The sort order to return results with.
/// </summary>
/// <value>The sort order.</value>
public SortOrder? SortOrder { get; set; }
diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
index 69c43efd4..789de3198 100644
--- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
+++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
@@ -9,21 +9,29 @@ namespace MediaBrowser.Model.LiveTv
public class LiveTvOptions
{
public int? GuideDays { get; set; }
+
public string RecordingPath { get; set; }
+
public string MovieRecordingPath { get; set; }
+
public string SeriesRecordingPath { get; set; }
+
public bool EnableRecordingSubfolders { get; set; }
+
public bool EnableOriginalAudioWithEncodedRecordings { get; set; }
public TunerHostInfo[] TunerHosts { get; set; }
+
public ListingsProviderInfo[] ListingProviders { get; set; }
public int PrePaddingSeconds { get; set; }
+
public int PostPaddingSeconds { get; set; }
public string[] MediaLocationsCreated { get; set; }
public string RecordingPostProcessor { get; set; }
+
public string RecordingPostProcessorArguments { get; set; }
public LiveTvOptions()
@@ -38,15 +46,25 @@ namespace MediaBrowser.Model.LiveTv
public class TunerHostInfo
{
public string Id { get; set; }
+
public string Url { get; set; }
+
public string Type { get; set; }
+
public string DeviceId { get; set; }
+
public string FriendlyName { get; set; }
+
public bool ImportFavoritesOnly { get; set; }
+
public bool AllowHWTranscoding { get; set; }
+
public bool EnableStreamLooping { get; set; }
+
public string Source { get; set; }
+
public int TunerCount { get; set; }
+
public string UserAgent { get; set; }
public TunerHostInfo()
@@ -58,23 +76,39 @@ namespace MediaBrowser.Model.LiveTv
public class ListingsProviderInfo
{
public string Id { get; set; }
+
public string Type { get; set; }
+
public string Username { get; set; }
+
public string Password { get; set; }
+
public string ListingsId { get; set; }
+
public string ZipCode { get; set; }
+
public string Country { get; set; }
+
public string Path { get; set; }
public string[] EnabledTuners { get; set; }
+
public bool EnableAllTuners { get; set; }
+
public string[] NewsCategories { get; set; }
+
public string[] SportsCategories { get; set; }
+
public string[] KidsCategories { get; set; }
+
public string[] MovieCategories { get; set; }
+
public NameValuePair[] ChannelMappings { get; set; }
+
public string MoviePrefix { get; set; }
+
public string PreferredLanguage { get; set; }
+
public string UserAgent { get; set; }
public ListingsProviderInfo()
diff --git a/MediaBrowser.Model/LiveTv/RecordingQuery.cs b/MediaBrowser.Model/LiveTv/RecordingQuery.cs
index 264982930..69e7db470 100644
--- a/MediaBrowser.Model/LiveTv/RecordingQuery.cs
+++ b/MediaBrowser.Model/LiveTv/RecordingQuery.cs
@@ -37,7 +37,7 @@ namespace MediaBrowser.Model.LiveTv
public int? StartIndex { get; set; }
/// <summary>
- /// The maximum number of items to return
+ /// The maximum number of items to return.
/// </summary>
/// <value>The limit.</value>
public int? Limit { get; set; }
@@ -61,18 +61,27 @@ namespace MediaBrowser.Model.LiveTv
public string SeriesTimerId { get; set; }
/// <summary>
- /// Fields to return within the items, in addition to basic information
+ /// Fields to return within the items, in addition to basic information.
/// </summary>
/// <value>The fields.</value>
public ItemFields[] Fields { get; set; }
+
public bool? EnableImages { get; set; }
+
public bool? IsLibraryItem { get; set; }
+
public bool? IsNews { get; set; }
+
public bool? IsMovie { get; set; }
+
public bool? IsSeries { get; set; }
+
public bool? IsKids { get; set; }
+
public bool? IsSports { get; set; }
+
public int? ImageTypeLimit { get; set; }
+
public ImageType[] EnableImageTypes { get; set; }
public bool EnableTotalRecordCount { get; set; }
diff --git a/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs b/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs
index bda46dd2b..b899a464b 100644
--- a/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs
+++ b/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs
@@ -7,7 +7,7 @@ namespace MediaBrowser.Model.LiveTv
public class SeriesTimerQuery
{
/// <summary>
- /// Gets or sets the sort by - SortName, Priority
+ /// Gets or sets the sort by - SortName, Priority.
/// </summary>
/// <value>The sort by.</value>
public string? SortBy { get; set; }
diff --git a/MediaBrowser.Model/LiveTv/TimerInfoDto.cs b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs
index 19039d448..0b9b4141a 100644
--- a/MediaBrowser.Model/LiveTv/TimerInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs
@@ -41,6 +41,5 @@ namespace MediaBrowser.Model.LiveTv
/// </summary>
/// <value>The program information.</value>
public BaseItemDto ProgramInfo { get; set; }
-
}
}
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index 83bd0c07e..902e29b20 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -23,7 +23,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
- <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.5" />
+ <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.6" />
<PackageReference Include="System.Globalization" Version="4.3.0" />
<PackageReference Include="System.Text.Json" Version="4.7.2" />
</ItemGroup>
diff --git a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs
index cce508809..83bda5d56 100644
--- a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs
+++ b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs
@@ -32,18 +32,29 @@ namespace MediaBrowser.Model.MediaInfo
}
public string OpenToken { get; set; }
+
public Guid UserId { get; set; }
+
public string PlaySessionId { get; set; }
+
public long? MaxStreamingBitrate { get; set; }
+
public long? StartTimeTicks { get; set; }
+
public int? AudioStreamIndex { get; set; }
+
public int? SubtitleStreamIndex { get; set; }
+
public int? MaxAudioChannels { get; set; }
+
public Guid ItemId { get; set; }
+
public DeviceProfile DeviceProfile { get; set; }
public bool EnableDirectPlay { get; set; }
+
public bool EnableDirectStream { get; set; }
+
public MediaProtocol[] DirectPlayProtocols { get; set; }
}
}
diff --git a/MediaBrowser.Model/MediaInfo/MediaInfo.cs b/MediaBrowser.Model/MediaInfo/MediaInfo.cs
index 97b979935..472055c22 100644
--- a/MediaBrowser.Model/MediaInfo/MediaInfo.cs
+++ b/MediaBrowser.Model/MediaInfo/MediaInfo.cs
@@ -35,13 +35,21 @@ namespace MediaBrowser.Model.MediaInfo
/// </summary>
/// <value>The studios.</value>
public string[] Studios { get; set; }
+
public string[] Genres { get; set; }
+
public string ShowName { get; set; }
+
public int? IndexNumber { get; set; }
+
public int? ParentIndexNumber { get; set; }
+
public int? ProductionYear { get; set; }
+
public DateTime? PremiereDate { get; set; }
+
public BaseItemPerson[] People { get; set; }
+
public Dictionary<string, string> ProviderIds { get; set; }
/// <summary>
diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs
index 82e13e0eb..321685677 100644
--- a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs
+++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs
@@ -29,11 +29,17 @@ namespace MediaBrowser.Model.MediaInfo
public DeviceProfile DeviceProfile { get; set; }
public bool EnableDirectPlay { get; set; }
+
public bool EnableDirectStream { get; set; }
+
public bool EnableTranscoding { get; set; }
+
public bool AllowVideoStreamCopy { get; set; }
+
public bool AllowAudioStreamCopy { get; set; }
+
public bool IsPlayback { get; set; }
+
public bool AutoOpenLiveStream { get; set; }
public MediaProtocol[] DirectPlayProtocols { get; set; }
diff --git a/MediaBrowser.Model/Net/HttpException.cs b/MediaBrowser.Model/Net/HttpException.cs
index 4b15e30f0..48ff5d51c 100644
--- a/MediaBrowser.Model/Net/HttpException.cs
+++ b/MediaBrowser.Model/Net/HttpException.cs
@@ -28,7 +28,6 @@ namespace MediaBrowser.Model.Net
public HttpException(string message, Exception innerException)
: base(message, innerException)
{
-
}
/// <summary>
diff --git a/MediaBrowser.Model/Net/NetworkShare.cs b/MediaBrowser.Model/Net/NetworkShare.cs
index a40cf73e4..6344cbe21 100644
--- a/MediaBrowser.Model/Net/NetworkShare.cs
+++ b/MediaBrowser.Model/Net/NetworkShare.cs
@@ -6,27 +6,27 @@ namespace MediaBrowser.Model.Net
public class NetworkShare
{
/// <summary>
- /// The name of the computer that this share belongs to
+ /// The name of the computer that this share belongs to.
/// </summary>
public string Server { get; set; }
/// <summary>
- /// Share name
+ /// Share name.
/// </summary>
public string Name { get; set; }
/// <summary>
- /// Local path
+ /// Local path.
/// </summary>
public string Path { get; set; }
/// <summary>
- /// Share type
+ /// Share type.
/// </summary>
public NetworkShareType ShareType { get; set; }
/// <summary>
- /// Comment
+ /// Comment.
/// </summary>
public string Remark { get; set; }
}
diff --git a/MediaBrowser.Model/Notifications/NotificationOption.cs b/MediaBrowser.Model/Notifications/NotificationOption.cs
index 144949a3b..ea363d9b1 100644
--- a/MediaBrowser.Model/Notifications/NotificationOption.cs
+++ b/MediaBrowser.Model/Notifications/NotificationOption.cs
@@ -18,7 +18,7 @@ namespace MediaBrowser.Model.Notifications
public string Type { get; set; }
/// <summary>
- /// User Ids to not monitor (it's opt out)
+ /// User Ids to not monitor (it's opt out).
/// </summary>
public string[] DisabledMonitorUsers { get; set; }
diff --git a/MediaBrowser.Model/Plugins/PluginInfo.cs b/MediaBrowser.Model/Plugins/PluginInfo.cs
index c13f1a89f..dd215192f 100644
--- a/MediaBrowser.Model/Plugins/PluginInfo.cs
+++ b/MediaBrowser.Model/Plugins/PluginInfo.cs
@@ -35,6 +35,12 @@ namespace MediaBrowser.Model.Plugins
/// </summary>
/// <value>The unique id.</value>
public string Id { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the plugin can be uninstalled.
+ /// </summary>
+ public bool CanUninstall { get; set; }
+
/// <summary>
/// Gets or sets the image URL.
/// </summary>
diff --git a/MediaBrowser.Model/Providers/ExternalIdInfo.cs b/MediaBrowser.Model/Providers/ExternalIdInfo.cs
index f2e6d8ef3..01784554f 100644
--- a/MediaBrowser.Model/Providers/ExternalIdInfo.cs
+++ b/MediaBrowser.Model/Providers/ExternalIdInfo.cs
@@ -1,26 +1,36 @@
-#nullable disable
-#pragma warning disable CS1591
-
namespace MediaBrowser.Model.Providers
{
+ /// <summary>
+ /// Represents the external id information for serialization to the client.
+ /// </summary>
public class ExternalIdInfo
{
/// <summary>
- /// Gets or sets the name.
+ /// Gets or sets the display name of the external id provider (IE: IMDB, MusicBrainz, etc).
+ /// </summary>
+ // TODO: This should be renamed to ProviderName
+ public string? Name { get; set; }
+
+ /// <summary>
+ /// Gets or sets the unique key for this id. This key should be unique across all providers.
/// </summary>
- /// <value>The name.</value>
- public string Name { get; set; }
+ // TODO: This property is not actually unique across the concrete types at the moment. It should be updated to be unique.
+ public string? Key { get; set; }
/// <summary>
- /// Gets or sets the key.
+ /// Gets or sets the specific media type for this id. This is used to distinguish between the different
+ /// external id types for providers with multiple ids.
+ /// A null value indicates there is no specific media type associated with the external id, or this is the
+ /// default id for the external provider so there is no need to specify a type.
/// </summary>
- /// <value>The key.</value>
- public string Key { get; set; }
+ /// <remarks>
+ /// This can be used along with the <see cref="Name"/> to localize the external id on the client.
+ /// </remarks>
+ public ExternalIdMediaType? Type { get; set; }
/// <summary>
/// Gets or sets the URL format string.
/// </summary>
- /// <value>The URL format string.</value>
- public string UrlFormatString { get; set; }
+ public string? UrlFormatString { get; set; }
}
}
diff --git a/MediaBrowser.Model/Providers/ExternalIdMediaType.cs b/MediaBrowser.Model/Providers/ExternalIdMediaType.cs
new file mode 100644
index 000000000..5303c8f58
--- /dev/null
+++ b/MediaBrowser.Model/Providers/ExternalIdMediaType.cs
@@ -0,0 +1,71 @@
+namespace MediaBrowser.Model.Providers
+{
+ /// <summary>
+ /// The specific media type of an <see cref="ExternalIdInfo"/>.
+ /// </summary>
+ /// <remarks>
+ /// Client applications may use this as a translation key.
+ /// </remarks>
+ public enum ExternalIdMediaType
+ {
+ /// <summary>
+ /// A music album.
+ /// </summary>
+ Album = 1,
+
+ /// <summary>
+ /// The artist of a music album.
+ /// </summary>
+ AlbumArtist = 2,
+
+ /// <summary>
+ /// The artist of a media item.
+ /// </summary>
+ Artist = 3,
+
+ /// <summary>
+ /// A boxed set of media.
+ /// </summary>
+ BoxSet = 4,
+
+ /// <summary>
+ /// A series episode.
+ /// </summary>
+ Episode = 5,
+
+ /// <summary>
+ /// A movie.
+ /// </summary>
+ Movie = 6,
+
+ /// <summary>
+ /// An alternative artist apart from the main artist.
+ /// </summary>
+ OtherArtist = 7,
+
+ /// <summary>
+ /// A person.
+ /// </summary>
+ Person = 8,
+
+ /// <summary>
+ /// A release group.
+ /// </summary>
+ ReleaseGroup = 9,
+
+ /// <summary>
+ /// A single season of a series.
+ /// </summary>
+ Season = 10,
+
+ /// <summary>
+ /// A series.
+ /// </summary>
+ Series = 11,
+
+ /// <summary>
+ /// A music track.
+ /// </summary>
+ Track = 12
+ }
+}
diff --git a/MediaBrowser.Model/Providers/RemoteSearchResult.cs b/MediaBrowser.Model/Providers/RemoteSearchResult.cs
index c96eb0b59..989741c01 100644
--- a/MediaBrowser.Model/Providers/RemoteSearchResult.cs
+++ b/MediaBrowser.Model/Providers/RemoteSearchResult.cs
@@ -51,6 +51,5 @@ namespace MediaBrowser.Model.Providers
public RemoteSearchResult[] Artists { get; set; }
-
}
}
diff --git a/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs b/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs
index d9f7a852c..a8d88d8a1 100644
--- a/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs
+++ b/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs
@@ -8,15 +8,25 @@ namespace MediaBrowser.Model.Providers
public class RemoteSubtitleInfo
{
public string ThreeLetterISOLanguageName { get; set; }
+
public string Id { get; set; }
+
public string ProviderName { get; set; }
+
public string Name { get; set; }
+
public string Format { get; set; }
+
public string Author { get; set; }
+
public string Comment { get; set; }
+
public DateTime? DateCreated { get; set; }
+
public float? CommunityRating { get; set; }
+
public int? DownloadCount { get; set; }
+
public bool? IsHashMatch { get; set; }
}
}
diff --git a/MediaBrowser.Model/Providers/SubtitleOptions.cs b/MediaBrowser.Model/Providers/SubtitleOptions.cs
index c07379570..5702c460b 100644
--- a/MediaBrowser.Model/Providers/SubtitleOptions.cs
+++ b/MediaBrowser.Model/Providers/SubtitleOptions.cs
@@ -8,13 +8,19 @@ namespace MediaBrowser.Model.Providers
public class SubtitleOptions
{
public bool SkipIfEmbeddedSubtitlesPresent { get; set; }
+
public bool SkipIfAudioTrackMatches { get; set; }
+
public string[] DownloadLanguages { get; set; }
+
public bool DownloadMovieSubtitles { get; set; }
+
public bool DownloadEpisodeSubtitles { get; set; }
public string OpenSubtitlesUsername { get; set; }
+
public string OpenSubtitlesPasswordHash { get; set; }
+
public bool IsOpenSubtitleVipAccount { get; set; }
public bool RequirePerfectMatch { get; set; }
diff --git a/MediaBrowser.Model/Providers/SubtitleProviderInfo.cs b/MediaBrowser.Model/Providers/SubtitleProviderInfo.cs
index ee25be4b6..7a7e7b9ec 100644
--- a/MediaBrowser.Model/Providers/SubtitleProviderInfo.cs
+++ b/MediaBrowser.Model/Providers/SubtitleProviderInfo.cs
@@ -6,6 +6,7 @@ namespace MediaBrowser.Model.Providers
public class SubtitleProviderInfo
{
public string Name { get; set; }
+
public string Id { get; set; }
}
}
diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs
index d7cc5ebbe..731d22aaf 100644
--- a/MediaBrowser.Model/Querying/ItemFields.cs
+++ b/MediaBrowser.Model/Querying/ItemFields.cs
@@ -8,198 +8,198 @@ namespace MediaBrowser.Model.Querying
public enum ItemFields
{
/// <summary>
- /// The air time
+ /// The air time.
/// </summary>
AirTime,
/// <summary>
- /// The can delete
+ /// The can delete.
/// </summary>
CanDelete,
/// <summary>
- /// The can download
+ /// The can download.
/// </summary>
CanDownload,
/// <summary>
- /// The channel information
+ /// The channel information.
/// </summary>
ChannelInfo,
/// <summary>
- /// The chapters
+ /// The chapters.
/// </summary>
Chapters,
ChildCount,
/// <summary>
- /// The cumulative run time ticks
+ /// The cumulative run time ticks.
/// </summary>
CumulativeRunTimeTicks,
/// <summary>
- /// The custom rating
+ /// The custom rating.
/// </summary>
CustomRating,
/// <summary>
- /// The date created of the item
+ /// The date created of the item.
/// </summary>
DateCreated,
/// <summary>
- /// The date last media added
+ /// The date last media added.
/// </summary>
DateLastMediaAdded,
/// <summary>
- /// Item display preferences
+ /// Item display preferences.
/// </summary>
DisplayPreferencesId,
/// <summary>
- /// The etag
+ /// The etag.
/// </summary>
Etag,
/// <summary>
- /// The external urls
+ /// The external urls.
/// </summary>
ExternalUrls,
/// <summary>
- /// Genres
+ /// Genres.
/// </summary>
Genres,
/// <summary>
- /// The home page URL
+ /// The home page URL.
/// </summary>
HomePageUrl,
/// <summary>
- /// The item counts
+ /// The item counts.
/// </summary>
ItemCounts,
/// <summary>
- /// The media source count
+ /// The media source count.
/// </summary>
MediaSourceCount,
/// <summary>
- /// The media versions
+ /// The media versions.
/// </summary>
MediaSources,
OriginalTitle,
/// <summary>
- /// The item overview
+ /// The item overview.
/// </summary>
Overview,
/// <summary>
- /// The id of the item's parent
+ /// The id of the item's parent.
/// </summary>
ParentId,
/// <summary>
- /// The physical path of the item
+ /// The physical path of the item.
/// </summary>
Path,
/// <summary>
- /// The list of people for the item
+ /// The list of people for the item.
/// </summary>
People,
PlayAccess,
/// <summary>
- /// The production locations
+ /// The production locations.
/// </summary>
ProductionLocations,
/// <summary>
- /// Imdb, tmdb, etc
+ /// Imdb, tmdb, etc.
/// </summary>
ProviderIds,
/// <summary>
- /// The aspect ratio of the primary image
+ /// The aspect ratio of the primary image.
/// </summary>
PrimaryImageAspectRatio,
RecursiveItemCount,
/// <summary>
- /// The settings
+ /// The settings.
/// </summary>
Settings,
/// <summary>
- /// The screenshot image tags
+ /// The screenshot image tags.
/// </summary>
ScreenshotImageTags,
SeriesPrimaryImage,
/// <summary>
- /// The series studio
+ /// The series studio.
/// </summary>
SeriesStudio,
/// <summary>
- /// The sort name of the item
+ /// The sort name of the item.
/// </summary>
SortName,
/// <summary>
- /// The special episode numbers
+ /// The special episode numbers.
/// </summary>
SpecialEpisodeNumbers,
/// <summary>
- /// The studios of the item
+ /// The studios of the item.
/// </summary>
Studios,
BasicSyncInfo,
/// <summary>
- /// The synchronize information
+ /// The synchronize information.
/// </summary>
SyncInfo,
/// <summary>
- /// The taglines of the item
+ /// The taglines of the item.
/// </summary>
Taglines,
/// <summary>
- /// The tags
+ /// The tags.
/// </summary>
Tags,
/// <summary>
- /// The trailer url of the item
+ /// The trailer url of the item.
/// </summary>
RemoteTrailers,
/// <summary>
- /// The media streams
+ /// The media streams.
/// </summary>
MediaStreams,
/// <summary>
- /// The season user data
+ /// The season user data.
/// </summary>
SeasonUserData,
/// <summary>
- /// The service name
+ /// The service name.
/// </summary>
ServiceName,
ThemeSongIds,
diff --git a/MediaBrowser.Model/Querying/ItemSortBy.cs b/MediaBrowser.Model/Querying/ItemSortBy.cs
index edf71c1a7..0b846bb96 100644
--- a/MediaBrowser.Model/Querying/ItemSortBy.cs
+++ b/MediaBrowser.Model/Querying/ItemSortBy.cs
@@ -3,7 +3,7 @@
namespace MediaBrowser.Model.Querying
{
/// <summary>
- /// These represent sort orders that are known by the core
+ /// These represent sort orders that are known by the core.
/// </summary>
public static class ItemSortBy
{
diff --git a/MediaBrowser.Model/Querying/NextUpQuery.cs b/MediaBrowser.Model/Querying/NextUpQuery.cs
index 0df86cb22..ee13ffc16 100644
--- a/MediaBrowser.Model/Querying/NextUpQuery.cs
+++ b/MediaBrowser.Model/Querying/NextUpQuery.cs
@@ -33,13 +33,13 @@ namespace MediaBrowser.Model.Querying
public int? StartIndex { get; set; }
/// <summary>
- /// The maximum number of items to return
+ /// The maximum number of items to return.
/// </summary>
/// <value>The limit.</value>
public int? Limit { get; set; }
/// <summary>
- /// Fields to return within the items, in addition to basic information
+ /// Fields to return within the items, in addition to basic information.
/// </summary>
/// <value>The fields.</value>
public ItemFields[] Fields { get; set; }
diff --git a/MediaBrowser.Model/Querying/QueryFilters.cs b/MediaBrowser.Model/Querying/QueryFilters.cs
index e04208f76..6e4d25181 100644
--- a/MediaBrowser.Model/Querying/QueryFilters.cs
+++ b/MediaBrowser.Model/Querying/QueryFilters.cs
@@ -9,8 +9,11 @@ namespace MediaBrowser.Model.Querying
public class QueryFiltersLegacy
{
public string[] Genres { get; set; }
+
public string[] Tags { get; set; }
+
public string[] OfficialRatings { get; set; }
+
public int[] Years { get; set; }
public QueryFiltersLegacy()
@@ -21,9 +24,11 @@ namespace MediaBrowser.Model.Querying
Years = Array.Empty<int>();
}
}
+
public class QueryFilters
{
public NameGuidPair[] Genres { get; set; }
+
public string[] Tags { get; set; }
public QueryFilters()
diff --git a/MediaBrowser.Model/Querying/QueryResult.cs b/MediaBrowser.Model/Querying/QueryResult.cs
index 42586243d..490f48b84 100644
--- a/MediaBrowser.Model/Querying/QueryResult.cs
+++ b/MediaBrowser.Model/Querying/QueryResult.cs
@@ -15,7 +15,7 @@ namespace MediaBrowser.Model.Querying
public IReadOnlyList<T> Items { get; set; }
/// <summary>
- /// The total number of records available
+ /// The total number of records available.
/// </summary>
/// <value>The total record count.</value>
public int TotalRecordCount { get; set; }
diff --git a/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs b/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs
index ed1aa7ac6..2ef6f7c60 100644
--- a/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs
+++ b/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs
@@ -1,6 +1,7 @@
#nullable disable
#pragma warning disable CS1591
+using System;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.Querying
@@ -26,13 +27,13 @@ namespace MediaBrowser.Model.Querying
public int? StartIndex { get; set; }
/// <summary>
- /// The maximum number of items to return
+ /// The maximum number of items to return.
/// </summary>
/// <value>The limit.</value>
public int? Limit { get; set; }
/// <summary>
- /// Fields to return within the items, in addition to basic information
+ /// Fields to return within the items, in addition to basic information.
/// </summary>
/// <value>The fields.</value>
public ItemFields[] Fields { get; set; }
@@ -54,7 +55,7 @@ namespace MediaBrowser.Model.Querying
public UpcomingEpisodesQuery()
{
- EnableImageTypes = new ImageType[] { };
+ EnableImageTypes = Array.Empty<ImageType>();
}
}
}
diff --git a/MediaBrowser.Model/Search/SearchHint.cs b/MediaBrowser.Model/Search/SearchHint.cs
index c7a721df6..983dbd2bc 100644
--- a/MediaBrowser.Model/Search/SearchHint.cs
+++ b/MediaBrowser.Model/Search/SearchHint.cs
@@ -100,6 +100,7 @@ namespace MediaBrowser.Model.Search
public string MediaType { get; set; }
public DateTime? StartDate { get; set; }
+
public DateTime? EndDate { get; set; }
/// <summary>
diff --git a/MediaBrowser.Model/Search/SearchQuery.cs b/MediaBrowser.Model/Search/SearchQuery.cs
index 4470f1ad9..01ad319a4 100644
--- a/MediaBrowser.Model/Search/SearchQuery.cs
+++ b/MediaBrowser.Model/Search/SearchQuery.cs
@@ -8,7 +8,7 @@ namespace MediaBrowser.Model.Search
public class SearchQuery
{
/// <summary>
- /// The user to localize search results for
+ /// The user to localize search results for.
/// </summary>
/// <value>The user id.</value>
public Guid UserId { get; set; }
@@ -26,20 +26,27 @@ namespace MediaBrowser.Model.Search
public int? StartIndex { get; set; }
/// <summary>
- /// The maximum number of items to return
+ /// The maximum number of items to return.
/// </summary>
/// <value>The limit.</value>
public int? Limit { get; set; }
public bool IncludePeople { get; set; }
+
public bool IncludeMedia { get; set; }
+
public bool IncludeGenres { get; set; }
+
public bool IncludeStudios { get; set; }
+
public bool IncludeArtists { get; set; }
public string[] MediaTypes { get; set; }
+
public string[] IncludeItemTypes { get; set; }
+
public string[] ExcludeItemTypes { get; set; }
+
public string ParentId { get; set; }
public bool? IsMovie { get; set; }
diff --git a/MediaBrowser.Model/Services/ApiMemberAttribute.cs b/MediaBrowser.Model/Services/ApiMemberAttribute.cs
index 7c23eee44..63f3ecd55 100644
--- a/MediaBrowser.Model/Services/ApiMemberAttribute.cs
+++ b/MediaBrowser.Model/Services/ApiMemberAttribute.cs
@@ -58,7 +58,7 @@ namespace MediaBrowser.Model.Services
public string Route { get; set; }
/// <summary>
- /// Whether to exclude this property from being included in the ModelSchema
+ /// Whether to exclude this property from being included in the ModelSchema.
/// </summary>
public bool ExcludeInSchema { get; set; }
}
diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs
index f413f1e17..8bc1d3668 100644
--- a/MediaBrowser.Model/Services/IRequest.cs
+++ b/MediaBrowser.Model/Services/IRequest.cs
@@ -23,7 +23,7 @@ namespace MediaBrowser.Model.Services
string Verb { get; }
/// <summary>
- /// The request ContentType
+ /// The request ContentType.
/// </summary>
string ContentType { get; }
@@ -32,7 +32,7 @@ namespace MediaBrowser.Model.Services
string UserAgent { get; }
/// <summary>
- /// The expected Response ContentType for this request
+ /// The expected Response ContentType for this request.
/// </summary>
string ResponseContentType { get; set; }
@@ -55,7 +55,7 @@ namespace MediaBrowser.Model.Services
string RemoteIp { get; }
/// <summary>
- /// The value of the Authorization Header used to send the Api Key, null if not available
+ /// The value of the Authorization Header used to send the Api Key, null if not available.
/// </summary>
string Authorization { get; }
@@ -68,7 +68,7 @@ namespace MediaBrowser.Model.Services
long ContentLength { get; }
/// <summary>
- /// The value of the Referrer, null if not available
+ /// The value of the Referrer, null if not available.
/// </summary>
Uri UrlReferrer { get; }
}
@@ -76,9 +76,13 @@ namespace MediaBrowser.Model.Services
public interface IHttpFile
{
string Name { get; }
+
string FileName { get; }
+
long ContentLength { get; }
+
string ContentType { get; }
+
Stream InputStream { get; }
}
diff --git a/MediaBrowser.Model/Services/IRequiresRequestStream.cs b/MediaBrowser.Model/Services/IRequiresRequestStream.cs
index 622626edc..3e5f2da42 100644
--- a/MediaBrowser.Model/Services/IRequiresRequestStream.cs
+++ b/MediaBrowser.Model/Services/IRequiresRequestStream.cs
@@ -7,7 +7,7 @@ namespace MediaBrowser.Model.Services
public interface IRequiresRequestStream
{
/// <summary>
- /// The raw Http Request Input Stream
+ /// The raw Http Request Input Stream.
/// </summary>
Stream RequestStream { get; set; }
}
diff --git a/MediaBrowser.Model/Services/IService.cs b/MediaBrowser.Model/Services/IService.cs
index a26d39455..5233f57ab 100644
--- a/MediaBrowser.Model/Services/IService.cs
+++ b/MediaBrowser.Model/Services/IService.cs
@@ -8,6 +8,8 @@ namespace MediaBrowser.Model.Services
}
public interface IReturn { }
+
public interface IReturn<T> : IReturn { }
+
public interface IReturnVoid : IReturn { }
}
diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs
index d07ff1548..bdb0cabdf 100644
--- a/MediaBrowser.Model/Services/QueryParamCollection.cs
+++ b/MediaBrowser.Model/Services/QueryParamCollection.cs
@@ -124,8 +124,8 @@ namespace MediaBrowser.Model.Services
/// Gets or sets a query parameter value by name. A query may contain multiple values of the same name
/// (i.e. "x=1&amp;x=2"), in which case the value is an array, which works for both getting and setting.
/// </summary>
- /// <param name="name">The query parameter name</param>
- /// <returns>The query parameter value or array of values</returns>
+ /// <param name="name">The query parameter name.</param>
+ /// <returns>The query parameter value or array of values.</returns>
public string this[string name]
{
get => Get(name);
diff --git a/MediaBrowser.Model/Services/RouteAttribute.cs b/MediaBrowser.Model/Services/RouteAttribute.cs
index 162576aa7..f8bf51112 100644
--- a/MediaBrowser.Model/Services/RouteAttribute.cs
+++ b/MediaBrowser.Model/Services/RouteAttribute.cs
@@ -128,9 +128,21 @@ namespace MediaBrowser.Model.Services
public override bool Equals(object obj)
{
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- if (obj.GetType() != this.GetType()) return false;
+ if (ReferenceEquals(null, obj))
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, obj))
+ {
+ return true;
+ }
+
+ if (obj.GetType() != this.GetType())
+ {
+ return false;
+ }
+
return Equals((RouteAttribute)obj);
}
diff --git a/MediaBrowser.Model/Session/ClientCapabilities.cs b/MediaBrowser.Model/Session/ClientCapabilities.cs
index 51db66d21..d3878ca30 100644
--- a/MediaBrowser.Model/Session/ClientCapabilities.cs
+++ b/MediaBrowser.Model/Session/ClientCapabilities.cs
@@ -13,15 +13,19 @@ namespace MediaBrowser.Model.Session
public string[] SupportedCommands { get; set; }
public bool SupportsMediaControl { get; set; }
+
public bool SupportsContentUploading { get; set; }
+
public string MessageCallbackUrl { get; set; }
public bool SupportsPersistentIdentifier { get; set; }
+
public bool SupportsSync { get; set; }
public DeviceProfile DeviceProfile { get; set; }
public string AppStoreUrl { get; set; }
+
public string IconUrl { get; set; }
public ClientCapabilities()
diff --git a/MediaBrowser.Model/Session/PlayRequest.cs b/MediaBrowser.Model/Session/PlayRequest.cs
index 62b68b49e..d57bed171 100644
--- a/MediaBrowser.Model/Session/PlayRequest.cs
+++ b/MediaBrowser.Model/Session/PlayRequest.cs
@@ -7,7 +7,7 @@ using MediaBrowser.Model.Services;
namespace MediaBrowser.Model.Session
{
/// <summary>
- /// Class PlayRequest
+ /// Class PlayRequest.
/// </summary>
public class PlayRequest
{
@@ -19,7 +19,7 @@ namespace MediaBrowser.Model.Session
public Guid[] ItemIds { get; set; }
/// <summary>
- /// Gets or sets the start position ticks that the first item should be played at
+ /// Gets or sets the start position ticks that the first item should be played at.
/// </summary>
/// <value>The start position ticks.</value>
[ApiMember(Name = "StartPositionTicks", Description = "The starting position of the first item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
@@ -39,8 +39,11 @@ namespace MediaBrowser.Model.Session
public Guid ControllingUserId { get; set; }
public int? SubtitleStreamIndex { get; set; }
+
public int? AudioStreamIndex { get; set; }
+
public string MediaSourceId { get; set; }
+
public int? StartIndex { get; set; }
}
}
diff --git a/MediaBrowser.Model/Session/PlaybackProgressInfo.cs b/MediaBrowser.Model/Session/PlaybackProgressInfo.cs
index 6b4cfe4f0..21bcabf1d 100644
--- a/MediaBrowser.Model/Session/PlaybackProgressInfo.cs
+++ b/MediaBrowser.Model/Session/PlaybackProgressInfo.cs
@@ -105,6 +105,7 @@ namespace MediaBrowser.Model.Session
public RepeatMode RepeatMode { get; set; }
public QueueItem[] NowPlayingQueue { get; set; }
+
public string PlaylistItemId { get; set; }
}
@@ -118,6 +119,7 @@ namespace MediaBrowser.Model.Session
public class QueueItem
{
public Guid Id { get; set; }
+
public string PlaylistItemId { get; set; }
}
}
diff --git a/MediaBrowser.Model/Session/PlaybackStopInfo.cs b/MediaBrowser.Model/Session/PlaybackStopInfo.cs
index b0827ac99..aa29bb249 100644
--- a/MediaBrowser.Model/Session/PlaybackStopInfo.cs
+++ b/MediaBrowser.Model/Session/PlaybackStopInfo.cs
@@ -62,6 +62,7 @@ namespace MediaBrowser.Model.Session
public string NextMediaType { get; set; }
public string PlaylistItemId { get; set; }
+
public QueueItem[] NowPlayingQueue { get; set; }
}
}
diff --git a/MediaBrowser.Model/Session/TranscodingInfo.cs b/MediaBrowser.Model/Session/TranscodingInfo.cs
index d6dc83413..e832c2f6f 100644
--- a/MediaBrowser.Model/Session/TranscodingInfo.cs
+++ b/MediaBrowser.Model/Session/TranscodingInfo.cs
@@ -8,17 +8,25 @@ namespace MediaBrowser.Model.Session
public class TranscodingInfo
{
public string AudioCodec { get; set; }
+
public string VideoCodec { get; set; }
+
public string Container { get; set; }
+
public bool IsVideoDirect { get; set; }
+
public bool IsAudioDirect { get; set; }
+
public int? Bitrate { get; set; }
public float? Framerate { get; set; }
+
public double? CompletionPercentage { get; set; }
public int? Width { get; set; }
+
public int? Height { get; set; }
+
public int? AudioChannels { get; set; }
public TranscodeReason[] TranscodeReasons { get; set; }
diff --git a/MediaBrowser.Model/Sync/SyncCategory.cs b/MediaBrowser.Model/Sync/SyncCategory.cs
index 215ac301e..80ad5f56e 100644
--- a/MediaBrowser.Model/Sync/SyncCategory.cs
+++ b/MediaBrowser.Model/Sync/SyncCategory.cs
@@ -5,15 +5,15 @@ namespace MediaBrowser.Model.Sync
public enum SyncCategory
{
/// <summary>
- /// The latest
+ /// The latest.
/// </summary>
Latest = 0,
/// <summary>
- /// The next up
+ /// The next up.
/// </summary>
NextUp = 1,
/// <summary>
- /// The resume
+ /// The resume.
/// </summary>
Resume = 2
}
diff --git a/MediaBrowser.Model/Sync/SyncJob.cs b/MediaBrowser.Model/Sync/SyncJob.cs
index 3cc9ff726..b9290b6e8 100644
--- a/MediaBrowser.Model/Sync/SyncJob.cs
+++ b/MediaBrowser.Model/Sync/SyncJob.cs
@@ -122,7 +122,9 @@ namespace MediaBrowser.Model.Sync
public int ItemCount { get; set; }
public string ParentName { get; set; }
+
public string PrimaryImageItemId { get; set; }
+
public string PrimaryImageTag { get; set; }
public SyncJob()
diff --git a/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs b/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs
index 89d245787..c749f7b13 100644
--- a/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs
+++ b/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs
@@ -9,42 +9,52 @@ namespace MediaBrowser.Model.SyncPlay
/// The user-joined update. Tells members of a group about a new user.
/// </summary>
UserJoined,
+
/// <summary>
/// The user-left update. Tells members of a group that a user left.
/// </summary>
UserLeft,
+
/// <summary>
/// The group-joined update. Tells a user that the group has been joined.
/// </summary>
GroupJoined,
+
/// <summary>
/// The group-left update. Tells a user that the group has been left.
/// </summary>
GroupLeft,
+
/// <summary>
/// The group-wait update. Tells members of the group that a user is buffering.
/// </summary>
GroupWait,
+
/// <summary>
/// The prepare-session update. Tells a user to load some content.
/// </summary>
PrepareSession,
+
/// <summary>
/// The not-in-group error. Tells a user that they don't belong to a group.
/// </summary>
NotInGroup,
+
/// <summary>
/// The group-does-not-exist error. Sent when trying to join a non-existing group.
/// </summary>
GroupDoesNotExist,
+
/// <summary>
/// The create-group-denied error. Sent when a user tries to create a group without required permissions.
/// </summary>
CreateGroupDenied,
+
/// <summary>
/// The join-group-denied error. Sent when a user tries to join a group without required permissions.
/// </summary>
JoinGroupDenied,
+
/// <summary>
/// The library-access-denied error. Sent when a user tries to join a group without required access to the library.
/// </summary>
diff --git a/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs b/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs
index d67b6bd55..0c77a6132 100644
--- a/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs
+++ b/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs
@@ -12,11 +12,5 @@ namespace MediaBrowser.Model.SyncPlay
/// </summary>
/// <value>The Group id to join.</value>
public Guid GroupId { get; set; }
-
- /// <summary>
- /// Gets or sets the playing item id.
- /// </summary>
- /// <value>The client's currently playing item id.</value>
- public Guid PlayingItemId { get; set; }
}
}
diff --git a/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs b/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs
index f1e175fde..e89efeed8 100644
--- a/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs
+++ b/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs
@@ -1,7 +1,7 @@
namespace MediaBrowser.Model.SyncPlay
{
/// <summary>
- /// Enum PlaybackRequestType
+ /// Enum PlaybackRequestType.
/// </summary>
public enum PlaybackRequestType
{
@@ -9,25 +9,30 @@ namespace MediaBrowser.Model.SyncPlay
/// A user is requesting a play command for the group.
/// </summary>
Play = 0,
+
/// <summary>
/// A user is requesting a pause command for the group.
/// </summary>
Pause = 1,
+
/// <summary>
/// A user is requesting a seek command for the group.
/// </summary>
Seek = 2,
+
/// <summary>
/// A user is signaling that playback is buffering.
/// </summary>
- Buffering = 3,
+ Buffer = 3,
+
/// <summary>
/// A user is signaling that playback resumed.
/// </summary>
- BufferingDone = 4,
+ Ready = 4,
+
/// <summary>
/// A user is reporting its ping.
/// </summary>
- UpdatePing = 5
+ Ping = 5
}
}
diff --git a/MediaBrowser.Model/SyncPlay/SendCommandType.cs b/MediaBrowser.Model/SyncPlay/SendCommandType.cs
index 113719871..86dec9e90 100644
--- a/MediaBrowser.Model/SyncPlay/SendCommandType.cs
+++ b/MediaBrowser.Model/SyncPlay/SendCommandType.cs
@@ -9,10 +9,12 @@ namespace MediaBrowser.Model.SyncPlay
/// The play command. Instructs users to start playback.
/// </summary>
Play = 0,
+
/// <summary>
/// The pause command. Instructs users to pause playback.
/// </summary>
Pause = 1,
+
/// <summary>
/// The seek command. Instructs users to seek to a specified time.
/// </summary>
diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs
index a67c38c3a..18ca74ee3 100644
--- a/MediaBrowser.Model/System/SystemInfo.cs
+++ b/MediaBrowser.Model/System/SystemInfo.cs
@@ -23,7 +23,7 @@ namespace MediaBrowser.Model.System
};
/// <summary>
- /// Class SystemInfo
+ /// Class SystemInfo.
/// </summary>
public class SystemInfo : PublicSystemInfo
{
diff --git a/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs b/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs
index c79d7fe75..b08acba2c 100644
--- a/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs
+++ b/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs
@@ -57,7 +57,7 @@ namespace MediaBrowser.Model.Tasks
double? CurrentProgress { get; }
/// <summary>
- /// Gets the triggers that define when the task will run
+ /// Gets the triggers that define when the task will run.
/// </summary>
/// <value>The triggers.</value>
/// <exception cref="ArgumentNullException">value</exception>
diff --git a/MediaBrowser.Model/Tasks/ITaskManager.cs b/MediaBrowser.Model/Tasks/ITaskManager.cs
index 4a7f579ec..363773ff7 100644
--- a/MediaBrowser.Model/Tasks/ITaskManager.cs
+++ b/MediaBrowser.Model/Tasks/ITaskManager.cs
@@ -10,7 +10,7 @@ namespace MediaBrowser.Model.Tasks
public interface ITaskManager : IDisposable
{
/// <summary>
- /// Gets the list of Scheduled Tasks
+ /// Gets the list of Scheduled Tasks.
/// </summary>
/// <value>The scheduled tasks.</value>
IScheduledTaskWorker[] ScheduledTasks { get; }
diff --git a/MediaBrowser.Model/Updates/RepositoryInfo.cs b/MediaBrowser.Model/Updates/RepositoryInfo.cs
new file mode 100644
index 000000000..bd42e77f0
--- /dev/null
+++ b/MediaBrowser.Model/Updates/RepositoryInfo.cs
@@ -0,0 +1,20 @@
+namespace MediaBrowser.Model.Updates
+{
+ /// <summary>
+ /// Class RepositoryInfo.
+ /// </summary>
+ public class RepositoryInfo
+ {
+ /// <summary>
+ /// Gets or sets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string? Name { get; set; }
+
+ /// <summary>
+ /// Gets or sets the URL.
+ /// </summary>
+ /// <value>The URL.</value>
+ public string? Url { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/Users/UserAction.cs b/MediaBrowser.Model/Users/UserAction.cs
index 36b8e6ee5..7646db4a8 100644
--- a/MediaBrowser.Model/Users/UserAction.cs
+++ b/MediaBrowser.Model/Users/UserAction.cs
@@ -8,11 +8,17 @@ namespace MediaBrowser.Model.Users
public class UserAction
{
public string Id { get; set; }
+
public string ServerId { get; set; }
+
public Guid UserId { get; set; }
+
public Guid ItemId { get; set; }
+
public UserActionType Type { get; set; }
+
public DateTime Date { get; set; }
+
public long? PositionTicks { get; set; }
}
}
diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs
index 2fd27d3b9..caf2e0f54 100644
--- a/MediaBrowser.Model/Users/UserPolicy.cs
+++ b/MediaBrowser.Model/Users/UserPolicy.cs
@@ -35,24 +35,37 @@ namespace MediaBrowser.Model.Users
public int? MaxParentalRating { get; set; }
public string[] BlockedTags { get; set; }
+
public bool EnableUserPreferenceAccess { get; set; }
+
public AccessSchedule[] AccessSchedules { get; set; }
+
public UnratedItem[] BlockUnratedItems { get; set; }
+
public bool EnableRemoteControlOfOtherUsers { get; set; }
+
public bool EnableSharedDeviceControl { get; set; }
+
public bool EnableRemoteAccess { get; set; }
public bool EnableLiveTvManagement { get; set; }
+
public bool EnableLiveTvAccess { get; set; }
public bool EnableMediaPlayback { get; set; }
+
public bool EnableAudioPlaybackTranscoding { get; set; }
+
public bool EnableVideoPlaybackTranscoding { get; set; }
+
public bool EnablePlaybackRemuxing { get; set; }
+
public bool ForceRemoteSourceTranscoding { get; set; }
public bool EnableContentDeletion { get; set; }
+
public string[] EnableContentDeletionFromFolders { get; set; }
+
public bool EnableContentDownloading { get; set; }
/// <summary>
@@ -60,29 +73,36 @@ namespace MediaBrowser.Model.Users
/// </summary>
/// <value><c>true</c> if [enable synchronize]; otherwise, <c>false</c>.</value>
public bool EnableSyncTranscoding { get; set; }
+
public bool EnableMediaConversion { get; set; }
public string[] EnabledDevices { get; set; }
+
public bool EnableAllDevices { get; set; }
public string[] EnabledChannels { get; set; }
+
public bool EnableAllChannels { get; set; }
public string[] EnabledFolders { get; set; }
+
public bool EnableAllFolders { get; set; }
public int InvalidLoginAttemptCount { get; set; }
+
public int LoginAttemptsBeforeLockout { get; set; }
public bool EnablePublicSharing { get; set; }
public string[] BlockedMediaFolders { get; set; }
+
public string[] BlockedChannels { get; set; }
public int RemoteClientBitrateLimit { get; set; }
[XmlElement(ElementName = "AuthenticationProviderId")]
public string AuthenticationProviderId { get; set; }
+
public string PasswordResetProviderId { get; set; }
/// <summary>
diff --git a/MediaBrowser.Providers/Books/AudioBookMetadataService.cs b/MediaBrowser.Providers/Books/AudioBookMetadataService.cs
index 4ff42429e..eabc66c6b 100644
--- a/MediaBrowser.Providers/Books/AudioBookMetadataService.cs
+++ b/MediaBrowser.Providers/Books/AudioBookMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
diff --git a/MediaBrowser.Providers/Books/BookMetadataService.cs b/MediaBrowser.Providers/Books/BookMetadataService.cs
index dcdf36f92..3f3782dfb 100644
--- a/MediaBrowser.Providers/Books/BookMetadataService.cs
+++ b/MediaBrowser.Providers/Books/BookMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
diff --git a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
index 46c9d6720..e5326da71 100644
--- a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
+++ b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Controller.Configuration;
diff --git a/MediaBrowser.Providers/Channels/ChannelMetadataService.cs b/MediaBrowser.Providers/Channels/ChannelMetadataService.cs
index b6abadc85..db2213bad 100644
--- a/MediaBrowser.Providers/Channels/ChannelMetadataService.cs
+++ b/MediaBrowser.Providers/Channels/ChannelMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
diff --git a/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs
index 0d00db3eb..46f368f72 100644
--- a/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs
+++ b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
diff --git a/MediaBrowser.Providers/Folders/FolderMetadataService.cs b/MediaBrowser.Providers/Folders/FolderMetadataService.cs
index be731e5c2..998bf4c6a 100644
--- a/MediaBrowser.Providers/Folders/FolderMetadataService.cs
+++ b/MediaBrowser.Providers/Folders/FolderMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
diff --git a/MediaBrowser.Providers/Folders/UserViewMetadataService.cs b/MediaBrowser.Providers/Folders/UserViewMetadataService.cs
index abb78fbc7..2d536f12e 100644
--- a/MediaBrowser.Providers/Folders/UserViewMetadataService.cs
+++ b/MediaBrowser.Providers/Folders/UserViewMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
diff --git a/MediaBrowser.Providers/Genres/GenreMetadataService.cs b/MediaBrowser.Providers/Genres/GenreMetadataService.cs
index 461f3fa2b..f7ea767e7 100644
--- a/MediaBrowser.Providers/Genres/GenreMetadataService.cs
+++ b/MediaBrowser.Providers/Genres/GenreMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
diff --git a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs b/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs
index ccf90d7ac..2e6cf4530 100644
--- a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs
+++ b/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs
index 3c94f6215..f655b8edd 100644
--- a/MediaBrowser.Providers/Manager/ImageSaver.cs
+++ b/MediaBrowser.Providers/Manager/ImageSaver.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -24,19 +26,19 @@ using Season = MediaBrowser.Controller.Entities.TV.Season;
namespace MediaBrowser.Providers.Manager
{
/// <summary>
- /// Class ImageSaver
+ /// Class ImageSaver.
/// </summary>
public class ImageSaver
{
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
/// <summary>
- /// The _config
+ /// The _config.
/// </summary>
private readonly IServerConfigurationManager _config;
/// <summary>
- /// The _directory watchers
+ /// The _directory watchers.
/// </summary>
private readonly ILibraryMonitor _libraryMonitor;
private readonly IFileSystem _fileSystem;
@@ -104,6 +106,7 @@ namespace MediaBrowser.Providers.Manager
}
}
}
+
if (saveLocallyWithMedia.HasValue && !saveLocallyWithMedia.Value)
{
saveLocally = saveLocallyWithMedia.Value;
@@ -147,6 +150,7 @@ namespace MediaBrowser.Providers.Manager
{
retryPath = retryPaths[currentPathIndex];
}
+
var savedPath = await SaveImageToLocation(source, path, retryPath, cancellationToken).ConfigureAwait(false);
savedPaths.Add(savedPath);
currentPathIndex++;
@@ -460,6 +464,7 @@ namespace MediaBrowser.Providers.Manager
{
filename = folderName;
}
+
path = Path.Combine(item.GetInternalMetadataPath(), filename + extension);
}
@@ -551,6 +556,7 @@ namespace MediaBrowser.Providers.Manager
{
list.Add(Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension));
}
+
return list.ToArray();
}
@@ -619,6 +625,7 @@ namespace MediaBrowser.Providers.Manager
{
imageFilename = "poster";
}
+
var folder = Path.GetDirectoryName(item.Path);
return Path.Combine(folder, Path.GetFileNameWithoutExtension(item.Path) + "-" + imageFilename + extension);
diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
index 48e1c94ad..6cc3ca369 100644
--- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs
+++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.IO;
@@ -58,6 +60,7 @@ namespace MediaBrowser.Providers.Manager
{
ClearImages(item, ImageType.Backdrop);
}
+
if (refreshOptions.IsReplacingImage(ImageType.Screenshot))
{
ClearImages(item, ImageType.Screenshot);
@@ -112,7 +115,10 @@ namespace MediaBrowser.Providers.Manager
foreach (var imageType in images)
{
- if (!IsEnabled(savedOptions, imageType, item)) continue;
+ if (!IsEnabled(savedOptions, imageType, item))
+ {
+ continue;
+ }
if (!HasImage(item, imageType) || (refreshOptions.IsReplacingImage(imageType) && !downloadedImages.Contains(imageType)))
{
@@ -168,7 +174,7 @@ namespace MediaBrowser.Providers.Manager
}
/// <summary>
- /// Image types that are only one per item
+ /// Image types that are only one per item.
/// </summary>
private readonly ImageType[] _singularImages =
{
@@ -189,7 +195,7 @@ namespace MediaBrowser.Providers.Manager
}
/// <summary>
- /// Determines if an item already contains the given images
+ /// Determines if an item already contains the given images.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="images">The images.</param>
@@ -221,6 +227,7 @@ namespace MediaBrowser.Providers.Manager
/// Refreshes from provider.
/// </summary>
/// <param name="item">The item.</param>
+ /// <param name="libraryOptions">The library options.</param>
/// <param name="provider">The provider.</param>
/// <param name="refreshOptions">The refresh options.</param>
/// <param name="savedOptions">The saved options.</param>
@@ -335,7 +342,6 @@ namespace MediaBrowser.Providers.Manager
}
catch (FileNotFoundException)
{
-
}
}
@@ -469,10 +475,12 @@ namespace MediaBrowser.Providers.Manager
catch (HttpException ex)
{
// Sometimes providers send back bad url's. Just move to the next image
- if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
+ if (ex.StatusCode.HasValue
+ && (ex.StatusCode.Value == HttpStatusCode.NotFound || ex.StatusCode.Value == HttpStatusCode.Forbidden))
{
continue;
}
+
break;
}
}
@@ -506,7 +514,7 @@ namespace MediaBrowser.Providers.Manager
return false;
}
- //if (!item.IsSaveLocalMetadataEnabled())
+ // if (!item.IsSaveLocalMetadataEnabled())
//{
// return true;
//}
@@ -529,7 +537,6 @@ namespace MediaBrowser.Providers.Manager
{
Path = path,
Type = imageType
-
}, newIndex);
}
@@ -583,10 +590,12 @@ namespace MediaBrowser.Providers.Manager
catch (HttpException ex)
{
// Sometimes providers send back bad urls. Just move onto the next image
- if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
+ if (ex.StatusCode.HasValue
+ && (ex.StatusCode.Value == HttpStatusCode.NotFound || ex.StatusCode.Value == HttpStatusCode.Forbidden))
{
continue;
}
+
break;
}
}
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index 4ea711a2a..3b0c7b56c 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
@@ -125,7 +127,7 @@ namespace MediaBrowser.Providers.Manager
ApplySearchResult(id, refreshOptions.SearchResult);
}
- //await FindIdentities(id, cancellationToken).ConfigureAwait(false);
+ // await FindIdentities(id, cancellationToken).ConfigureAwait(false);
id.IsAutomated = refreshOptions.IsAutomated;
var result = await RefreshWithProviders(metadataResult, id, refreshOptions, providers, itemImageProvider, cancellationToken).ConfigureAwait(false);
@@ -210,6 +212,7 @@ namespace MediaBrowser.Providers.Manager
LibraryManager.UpdatePeople(baseItem, result.People);
SavePeopleMetadata(result.People, libraryOptions, cancellationToken);
}
+
result.Item.UpdateToRepository(reason, cancellationToken);
}
@@ -252,7 +255,7 @@ namespace MediaBrowser.Providers.Manager
private void AddPersonImage(Person personEntity, LibraryOptions libraryOptions, string imageUrl, CancellationToken cancellationToken)
{
- //if (libraryOptions.DownloadImagesInAdvance)
+ // if (libraryOptions.DownloadImagesInAdvance)
//{
// try
// {
@@ -324,6 +327,7 @@ namespace MediaBrowser.Providers.Manager
{
return true;
}
+
var folder = item as Folder;
if (folder != null)
{
@@ -389,7 +393,7 @@ namespace MediaBrowser.Providers.Manager
{
if (!child.IsFolder)
{
- ticks += (child.RunTimeTicks ?? 0);
+ ticks += child.RunTimeTicks ?? 0;
}
}
@@ -422,6 +426,7 @@ namespace MediaBrowser.Providers.Manager
{
dateLastMediaAdded = childDateCreated;
}
+
any = true;
}
}
@@ -718,7 +723,7 @@ namespace MediaBrowser.Providers.Manager
userDataList.AddRange(localItem.UserDataList);
}
- MergeData(localItem, temp, new MetadataField[] { }, !options.ReplaceAllMetadata, true);
+ MergeData(localItem, temp, Array.Empty<MetadataField>(), !options.ReplaceAllMetadata, true);
refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport;
// Only one local provider allowed per item
@@ -726,6 +731,7 @@ namespace MediaBrowser.Providers.Manager
{
hasLocalMetadata = true;
}
+
break;
}
@@ -772,14 +778,14 @@ namespace MediaBrowser.Providers.Manager
}
}
- //var isUnidentified = failedProviderCount > 0 && successfulProviderCount == 0;
+ // var isUnidentified = failedProviderCount > 0 && successfulProviderCount == 0;
foreach (var provider in customProviders.Where(i => !(i is IPreRefreshProvider)))
{
await RunCustomProvider(provider, item, logName, options, refreshResult, cancellationToken).ConfigureAwait(false);
}
- //ImportUserData(item, userDataList, cancellationToken);
+ // ImportUserData(item, userDataList, cancellationToken);
return refreshResult;
}
@@ -843,7 +849,7 @@ namespace MediaBrowser.Providers.Manager
{
result.Provider = provider.Name;
- MergeData(result, temp, new MetadataField[] { }, false, false);
+ MergeData(result, temp, Array.Empty<MetadataField>(), false, false);
MergeNewData(temp.Item, id);
refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataDownload;
@@ -874,6 +880,7 @@ namespace MediaBrowser.Providers.Manager
{
return "en";
}
+
return language;
}
@@ -906,7 +913,7 @@ namespace MediaBrowser.Providers.Manager
{
var hasChanged = changeMonitor.HasChanged(item, directoryService);
- //if (hasChanged)
+ // if (hasChanged)
//{
// logger.LogDebug("{0} reports change to {1}", changeMonitor.GetType().Name, item.Path ?? item.Name);
//}
@@ -924,7 +931,9 @@ namespace MediaBrowser.Providers.Manager
public class RefreshResult
{
public ItemUpdateType UpdateType { get; set; }
+
public string ErrorMessage { get; set; }
+
public int Failures { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index eeb74ec96..4ee065cd1 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -25,7 +23,6 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
using Priority_Queue;
using Book = MediaBrowser.Controller.Entities.Book;
@@ -38,37 +35,42 @@ using Series = MediaBrowser.Controller.Entities.TV.Series;
namespace MediaBrowser.Providers.Manager
{
/// <summary>
- /// Class ProviderManager
+ /// Class ProviderManager.
/// </summary>
public class ProviderManager : IProviderManager, IDisposable
{
+ private readonly object _refreshQueueLock = new object();
private readonly ILogger<ProviderManager> _logger;
private readonly IHttpClient _httpClient;
private readonly ILibraryMonitor _libraryMonitor;
private readonly IFileSystem _fileSystem;
private readonly IServerApplicationPaths _appPaths;
- private readonly IJsonSerializer _json;
private readonly ILibraryManager _libraryManager;
private readonly ISubtitleManager _subtitleManager;
private readonly IServerConfigurationManager _configurationManager;
+ private readonly ConcurrentDictionary<Guid, double> _activeRefreshes = new ConcurrentDictionary<Guid, double>();
+ private readonly CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
+ private readonly SimplePriorityQueue<Tuple<Guid, MetadataRefreshOptions>> _refreshQueue =
+ new SimplePriorityQueue<Tuple<Guid, MetadataRefreshOptions>>();
- private IImageProvider[] ImageProviders { get; set; }
-
- private IMetadataService[] _metadataServices = { };
- private IMetadataProvider[] _metadataProviders = { };
+ private IMetadataService[] _metadataServices = Array.Empty<IMetadataService>();
+ private IMetadataProvider[] _metadataProviders = Array.Empty<IMetadataProvider>();
private IEnumerable<IMetadataSaver> _savers;
-
private IExternalId[] _externalIds;
-
- private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
-
- public event EventHandler<GenericEventArgs<BaseItem>> RefreshStarted;
- public event EventHandler<GenericEventArgs<BaseItem>> RefreshCompleted;
- public event EventHandler<GenericEventArgs<Tuple<BaseItem, double>>> RefreshProgress;
+ private bool _isProcessingRefreshQueue;
+ private bool _disposed;
/// <summary>
- /// Initializes a new instance of the <see cref="ProviderManager" /> class.
+ /// Initializes a new instance of the <see cref="ProviderManager"/> class.
/// </summary>
+ /// <param name="httpClient">The Http client.</param>
+ /// <param name="subtitleManager">The subtitle manager.</param>
+ /// <param name="configurationManager">The configuration manager.</param>
+ /// <param name="libraryMonitor">The library monitor.</param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="fileSystem">The filesystem.</param>
+ /// <param name="appPaths">The server application paths.</param>
+ /// <param name="libraryManager">The library manager.</param>
public ProviderManager(
IHttpClient httpClient,
ISubtitleManager subtitleManager,
@@ -77,8 +79,7 @@ namespace MediaBrowser.Providers.Manager
ILogger<ProviderManager> logger,
IFileSystem fileSystem,
IServerApplicationPaths appPaths,
- ILibraryManager libraryManager,
- IJsonSerializer json)
+ ILibraryManager libraryManager)
{
_logger = logger;
_httpClient = httpClient;
@@ -87,44 +88,45 @@ namespace MediaBrowser.Providers.Manager
_fileSystem = fileSystem;
_appPaths = appPaths;
_libraryManager = libraryManager;
- _json = json;
_subtitleManager = subtitleManager;
}
- /// <summary>
- /// Adds the metadata providers.
- /// </summary>
- public void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices,
- IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers,
- IEnumerable<IExternalId> externalIds)
+ /// <inheritdoc/>
+ public event EventHandler<GenericEventArgs<BaseItem>> RefreshStarted;
+
+ /// <inheritdoc/>
+ public event EventHandler<GenericEventArgs<BaseItem>> RefreshCompleted;
+
+ /// <inheritdoc/>
+ public event EventHandler<GenericEventArgs<Tuple<BaseItem, double>>> RefreshProgress;
+
+ private IImageProvider[] ImageProviders { get; set; }
+
+ /// <inheritdoc/>
+ public void AddParts(
+ IEnumerable<IImageProvider> imageProviders,
+ IEnumerable<IMetadataService> metadataServices,
+ IEnumerable<IMetadataProvider> metadataProviders,
+ IEnumerable<IMetadataSaver> metadataSavers,
+ IEnumerable<IExternalId> externalIds)
{
ImageProviders = imageProviders.ToArray();
_metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
_metadataProviders = metadataProviders.ToArray();
- _externalIds = externalIds.OrderBy(i => i.Name).ToArray();
+ _externalIds = externalIds.OrderBy(i => i.ProviderName).ToArray();
- _savers = metadataSavers.Where(i =>
- {
- var configurable = i as IConfigurableProvider;
-
- return configurable == null || configurable.IsEnabled;
- }).ToArray();
+ _savers = metadataSavers
+ .Where(i => !(i is IConfigurableProvider configurable) || configurable.IsEnabled)
+ .ToArray();
}
+ /// <inheritdoc/>
public Task<ItemUpdateType> RefreshSingleItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
- IMetadataService service = null;
var type = item.GetType();
- foreach (var current in _metadataServices)
- {
- if (current.CanRefreshPrimary(type))
- {
- service = current;
- break;
- }
- }
+ var service = _metadataServices.FirstOrDefault(current => current.CanRefreshPrimary(type));
if (service == null)
{
@@ -147,35 +149,36 @@ namespace MediaBrowser.Providers.Manager
return Task.FromResult(ItemUpdateType.None);
}
+ /// <inheritdoc/>
public async Task SaveImage(BaseItem item, string url, ImageType type, int? imageIndex, CancellationToken cancellationToken)
{
- using (var response = await _httpClient.GetResponse(new HttpRequestOptions
+ using var response = await _httpClient.GetResponse(new HttpRequestOptions
{
CancellationToken = cancellationToken,
Url = url,
BufferContent = false
+ }).ConfigureAwait(false);
- }).ConfigureAwait(false))
+ // Workaround for tvheadend channel icons
+ // TODO: Isolate this hack into the tvh plugin
+ if (string.IsNullOrEmpty(response.ContentType))
{
- // Workaround for tvheadend channel icons
- // TODO: Isolate this hack into the tvh plugin
- if (string.IsNullOrEmpty(response.ContentType))
+ if (url.IndexOf("/imagecache/", StringComparison.OrdinalIgnoreCase) != -1)
{
- if (url.IndexOf("/imagecache/", StringComparison.OrdinalIgnoreCase) != -1)
- {
- response.ContentType = "image/png";
- }
+ response.ContentType = "image/png";
}
-
- await SaveImage(item, response.Content, response.ContentType, type, imageIndex, cancellationToken).ConfigureAwait(false);
}
+
+ await SaveImage(item, response.Content, response.ContentType, type, imageIndex, cancellationToken).ConfigureAwait(false);
}
+ /// <inheritdoc/>
public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken)
{
return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, cancellationToken);
}
+ /// <inheritdoc/>
public Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(source))
@@ -188,12 +191,14 @@ namespace MediaBrowser.Providers.Manager
return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken);
}
+ /// <inheritdoc/>
public Task SaveImage(User user, Stream source, string mimeType, string path)
{
return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger)
.SaveImage(user, source, path);
}
+ /// <inheritdoc/>
public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(BaseItem item, RemoteImageQuery query, CancellationToken cancellationToken)
{
var providers = GetRemoteImageProviders(item, query.IncludeDisabledProviders);
@@ -213,7 +218,7 @@ namespace MediaBrowser.Providers.Manager
languages.Add(preferredLanguage);
}
- var tasks = providers.Select(i => GetImages(item, cancellationToken, i, languages, query.ImageType));
+ var tasks = providers.Select(i => GetImages(item, i, languages, cancellationToken, query.ImageType));
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
@@ -224,12 +229,17 @@ namespace MediaBrowser.Providers.Manager
/// Gets the images.
/// </summary>
/// <param name="item">The item.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
/// <param name="provider">The provider.</param>
/// <param name="preferredLanguages">The preferred languages.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
/// <param name="type">The type.</param>
/// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
- private async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken, IRemoteImageProvider provider, List<string> preferredLanguages, ImageType? type = null)
+ private async Task<IEnumerable<RemoteImageInfo>> GetImages(
+ BaseItem item,
+ IRemoteImageProvider provider,
+ IReadOnlyCollection<string> preferredLanguages,
+ CancellationToken cancellationToken,
+ ImageType? type = null)
{
try
{
@@ -260,16 +270,18 @@ namespace MediaBrowser.Providers.Manager
}
}
- /// <summary>
- /// Gets the supported image providers.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <returns>IEnumerable{IImageProvider}.</returns>
+ /// <inheritdoc/>
public IEnumerable<ImageProviderInfo> GetRemoteImageProviderInfo(BaseItem item)
{
return GetRemoteImageProviders(item, true).Select(i => new ImageProviderInfo(i.Name, i.GetSupportedImages(item).ToArray()));
}
+ /// <summary>
+ /// Gets the image providers for the provided item.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="refreshOptions">The image refresh options.</param>
+ /// <returns>The image providers for the item.</returns>
public IEnumerable<IImageProvider> GetImageProviders(BaseItem item, ImageRefreshOptions refreshOptions)
{
return GetImageProviders(item, _libraryManager.GetLibraryOptions(item), GetMetadataOptions(item), refreshOptions, false);
@@ -283,7 +295,7 @@ namespace MediaBrowser.Providers.Manager
var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
var typeFetcherOrder = typeOptions?.ImageFetcherOrder;
- return ImageProviders.Where(i => CanRefresh(i, item, libraryOptions, options, refreshOptions, includeDisabled))
+ return ImageProviders.Where(i => CanRefresh(i, item, libraryOptions, refreshOptions, includeDisabled))
.OrderBy(i =>
{
// See if there's a user-defined order
@@ -304,6 +316,13 @@ namespace MediaBrowser.Providers.Manager
.ThenBy(GetOrder);
}
+ /// <summary>
+ /// Gets the metadata providers for the provided item.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="libraryOptions">The library options.</param>
+ /// <typeparam name="T">The type of metadata provider.</typeparam>
+ /// <returns>The metadata providers.</returns>
public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(BaseItem item, LibraryOptions libraryOptions)
where T : BaseItem
{
@@ -319,7 +338,7 @@ namespace MediaBrowser.Providers.Manager
var currentOptions = globalMetadataOptions;
return _metadataProviders.OfType<IMetadataProvider<T>>()
- .Where(i => CanRefresh(i, item, libraryOptions, currentOptions, includeDisabled, forceEnableInternetMetadata))
+ .Where(i => CanRefresh(i, item, libraryOptions, includeDisabled, forceEnableInternetMetadata))
.OrderBy(i => GetConfiguredOrder(item, i, libraryOptions, globalMetadataOptions))
.ThenBy(GetDefaultOrder);
}
@@ -329,14 +348,20 @@ namespace MediaBrowser.Providers.Manager
var options = GetMetadataOptions(item);
var libraryOptions = _libraryManager.GetLibraryOptions(item);
- return GetImageProviders(item, libraryOptions, options,
- new ImageRefreshOptions(
- new DirectoryService(_fileSystem)),
- includeDisabled)
- .OfType<IRemoteImageProvider>();
+ return GetImageProviders(
+ item,
+ libraryOptions,
+ options,
+ new ImageRefreshOptions(new DirectoryService(_fileSystem)),
+ includeDisabled).OfType<IRemoteImageProvider>();
}
- private bool CanRefresh(IMetadataProvider provider, BaseItem item, LibraryOptions libraryOptions, MetadataOptions options, bool includeDisabled, bool forceEnableInternetMetadata)
+ private bool CanRefresh(
+ IMetadataProvider provider,
+ BaseItem item,
+ LibraryOptions libraryOptions,
+ bool includeDisabled,
+ bool forceEnableInternetMetadata)
{
if (!includeDisabled)
{
@@ -372,7 +397,12 @@ namespace MediaBrowser.Providers.Manager
return true;
}
- private bool CanRefresh(IImageProvider provider, BaseItem item, LibraryOptions libraryOptions, MetadataOptions options, ImageRefreshOptions refreshOptions, bool includeDisabled)
+ private bool CanRefresh(
+ IImageProvider provider,
+ BaseItem item,
+ LibraryOptions libraryOptions,
+ ImageRefreshOptions refreshOptions,
+ bool includeDisabled)
{
if (!includeDisabled)
{
@@ -412,9 +442,7 @@ namespace MediaBrowser.Providers.Manager
/// <returns>System.Int32.</returns>
private int GetOrder(IImageProvider provider)
{
- var hasOrder = provider as IHasOrder;
-
- if (hasOrder == null)
+ if (!(provider is IHasOrder hasOrder))
{
return 0;
}
@@ -441,7 +469,7 @@ namespace MediaBrowser.Providers.Manager
if (provider is IRemoteMetadataProvider)
{
var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
- var typeFetcherOrder = typeOptions == null ? null : typeOptions.MetadataFetcherOrder;
+ var typeFetcherOrder = typeOptions?.MetadataFetcherOrder;
var fetcherOrder = typeFetcherOrder ?? globalMetadataOptions.MetadataFetcherOrder;
@@ -459,9 +487,7 @@ namespace MediaBrowser.Providers.Manager
private int GetDefaultOrder(IMetadataProvider provider)
{
- var hasOrder = provider as IHasOrder;
-
- if (hasOrder != null)
+ if (provider is IHasOrder hasOrder)
{
return hasOrder.Order;
}
@@ -469,9 +495,10 @@ namespace MediaBrowser.Providers.Manager
return 0;
}
+ /// <inheritdoc/>
public MetadataPluginSummary[] GetAllMetadataPlugins()
{
- return new MetadataPluginSummary[]
+ return new[]
{
GetPluginSummary<Movie>(),
GetPluginSummary<BoxSet>(),
@@ -493,7 +520,7 @@ namespace MediaBrowser.Providers.Manager
where T : BaseItem, new()
{
// Give it a dummy path just so that it looks like a file system item
- var dummy = new T()
+ var dummy = new T
{
Path = Path.Combine(_appPaths.InternalMetadataPath, "dummy"),
ParentId = Guid.NewGuid()
@@ -508,11 +535,12 @@ namespace MediaBrowser.Providers.Manager
var libraryOptions = new LibraryOptions();
- var imageProviders = GetImageProviders(dummy, libraryOptions, options,
- new ImageRefreshOptions(
- new DirectoryService(_fileSystem)),
- true)
- .ToList();
+ var imageProviders = GetImageProviders(
+ dummy,
+ libraryOptions,
+ options,
+ new ImageRefreshOptions(new DirectoryService(_fileSystem)),
+ true).ToList();
var pluginList = summary.Plugins.ToList();
@@ -572,7 +600,6 @@ namespace MediaBrowser.Providers.Manager
private void AddImagePlugins<T>(List<MetadataPlugin> list, T item, List<IImageProvider> imageProviders)
where T : BaseItem
{
-
// Locals
list.AddRange(imageProviders.Where(i => (i is ILocalImageProvider)).Select(i => new MetadataPlugin
{
@@ -588,6 +615,7 @@ namespace MediaBrowser.Providers.Manager
}));
}
+ /// <inheritdoc/>
public MetadataOptions GetMetadataOptions(BaseItem item)
{
var type = item.GetType().Name;
@@ -597,17 +625,13 @@ namespace MediaBrowser.Providers.Manager
new MetadataOptions();
}
- /// <summary>
- /// Saves the metadata.
- /// </summary>
+ /// <inheritdoc/>
public void SaveMetadata(BaseItem item, ItemUpdateType updateType)
{
SaveMetadata(item, updateType, _savers);
}
- /// <summary>
- /// Saves the metadata.
- /// </summary>
+ /// <inheritdoc/>
public void SaveMetadata(BaseItem item, ItemUpdateType updateType, IEnumerable<string> savers)
{
SaveMetadata(item, updateType, _savers.Where(i => savers.Contains(i.Name, StringComparer.OrdinalIgnoreCase)));
@@ -619,7 +643,6 @@ namespace MediaBrowser.Providers.Manager
/// <param name="item">The item.</param>
/// <param name="updateType">Type of the update.</param>
/// <param name="savers">The savers.</param>
- /// <returns>Task.</returns>
private void SaveMetadata(BaseItem item, ItemUpdateType updateType, IEnumerable<IMetadataSaver> savers)
{
var libraryOptions = _libraryManager.GetLibraryOptions(item);
@@ -628,11 +651,9 @@ namespace MediaBrowser.Providers.Manager
{
_logger.LogDebug("Saving {0} to {1}.", item.Path ?? item.Name, saver.Name);
- var fileSaver = saver as IMetadataFileSaver;
-
- if (fileSaver != null)
+ if (saver is IMetadataFileSaver fileSaver)
{
- string path = null;
+ string path;
try
{
@@ -699,11 +720,9 @@ namespace MediaBrowser.Providers.Manager
{
if (updateType >= ItemUpdateType.MetadataEdit)
{
- var fileSaver = saver as IMetadataFileSaver;
-
// Manual edit occurred
// Even if save local is off, save locally anyway if the metadata file already exists
- if (fileSaver == null || !File.Exists(fileSaver.GetSavePath(item)))
+ if (!(saver is IMetadataFileSaver fileSaver) || !File.Exists(fileSaver.GetSavePath(item)))
{
return false;
}
@@ -734,6 +753,7 @@ namespace MediaBrowser.Providers.Manager
}
}
+ /// <inheritdoc/>
public Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TLookupType> searchInfo, CancellationToken cancellationToken)
where TItemType : BaseItem, new()
where TLookupType : ItemLookupInfo
@@ -748,7 +768,7 @@ namespace MediaBrowser.Providers.Manager
return GetRemoteSearchResults<TItemType, TLookupType>(searchInfo, referenceItem, cancellationToken);
}
- public async Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TLookupType> searchInfo, BaseItem referenceItem, CancellationToken cancellationToken)
+ private async Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TLookupType> searchInfo, BaseItem referenceItem, CancellationToken cancellationToken)
where TItemType : BaseItem, new()
where TLookupType : ItemLookupInfo
{
@@ -787,6 +807,7 @@ namespace MediaBrowser.Providers.Manager
{
searchInfo.SearchInfo.MetadataLanguage = _configurationManager.Configuration.PreferredMetadataLanguage;
}
+
if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataCountryCode))
{
searchInfo.SearchInfo.MetadataCountryCode = _configurationManager.Configuration.MetadataCountryCode;
@@ -831,12 +852,14 @@ namespace MediaBrowser.Providers.Manager
}
}
- //_logger.LogDebug("Returning search results {0}", _json.SerializeToString(resultList));
+ // _logger.LogDebug("Returning search results {0}", _json.SerializeToString(resultList));
return resultList;
}
- private async Task<IEnumerable<RemoteSearchResult>> GetSearchResults<TLookupType>(IRemoteSearchProvider<TLookupType> provider, TLookupType searchInfo,
+ private async Task<IEnumerable<RemoteSearchResult>> GetSearchResults<TLookupType>(
+ IRemoteSearchProvider<TLookupType> provider,
+ TLookupType searchInfo,
CancellationToken cancellationToken)
where TLookupType : ItemLookupInfo
{
@@ -852,6 +875,7 @@ namespace MediaBrowser.Providers.Manager
return list;
}
+ /// <inheritdoc/>
public Task<HttpResponseInfo> GetSearchImage(string providerName, string url, CancellationToken cancellationToken)
{
var provider = _metadataProviders.OfType<IRemoteSearchProvider>().FirstOrDefault(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase));
@@ -864,6 +888,7 @@ namespace MediaBrowser.Providers.Manager
return provider.GetImageResponse(url, cancellationToken);
}
+ /// <inheritdoc/>
public IEnumerable<IExternalId> GetExternalIds(IHasProviderIds item)
{
return _externalIds.Where(i =>
@@ -880,6 +905,7 @@ namespace MediaBrowser.Providers.Manager
});
}
+ /// <inheritdoc/>
public IEnumerable<ExternalUrl> GetExternalUrls(BaseItem item)
{
return GetExternalIds(item)
@@ -899,29 +925,29 @@ namespace MediaBrowser.Providers.Manager
return new ExternalUrl
{
- Name = i.Name,
+ Name = i.ProviderName,
Url = string.Format(
CultureInfo.InvariantCulture,
i.UrlFormatString,
value)
};
-
}).Where(i => i != null).Concat(item.GetRelatedUrls());
}
+ /// <inheritdoc/>
public IEnumerable<ExternalIdInfo> GetExternalIdInfos(IHasProviderIds item)
{
return GetExternalIds(item)
.Select(i => new ExternalIdInfo
{
- Name = i.Name,
+ Name = i.ProviderName,
Key = i.Key,
+ Type = i.Type,
UrlFormatString = i.UrlFormatString
});
}
- private ConcurrentDictionary<Guid, double> _activeRefreshes = new ConcurrentDictionary<Guid, double>();
-
+ /// <inheritdoc/>
public Dictionary<Guid, Guid> GetRefreshQueue()
{
lock (_refreshQueueLock)
@@ -937,6 +963,7 @@ namespace MediaBrowser.Providers.Manager
}
}
+ /// <inheritdoc/>
public void OnRefreshStart(BaseItem item)
{
_logger.LogInformation("OnRefreshStart {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
@@ -944,6 +971,7 @@ namespace MediaBrowser.Providers.Manager
RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
}
+ /// <inheritdoc/>
public void OnRefreshComplete(BaseItem item)
{
_logger.LogInformation("OnRefreshComplete {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
@@ -953,6 +981,7 @@ namespace MediaBrowser.Providers.Manager
RefreshCompleted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
}
+ /// <inheritdoc/>
public double? GetRefreshProgress(Guid id)
{
if (_activeRefreshes.TryGetValue(id, out double value))
@@ -963,6 +992,7 @@ namespace MediaBrowser.Providers.Manager
return null;
}
+ /// <inheritdoc/>
public void OnRefreshProgress(BaseItem item, double progress)
{
var id = item.Id;
@@ -982,12 +1012,7 @@ namespace MediaBrowser.Providers.Manager
RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(item, progress)));
}
- private readonly SimplePriorityQueue<Tuple<Guid, MetadataRefreshOptions>> _refreshQueue =
- new SimplePriorityQueue<Tuple<Guid, MetadataRefreshOptions>>();
-
- private readonly object _refreshQueueLock = new object();
- private bool _isProcessingRefreshQueue;
-
+ /// <inheritdoc/>
public void QueueRefresh(Guid id, MetadataRefreshOptions options, RefreshPriority priority)
{
if (_disposed)
@@ -1031,7 +1056,7 @@ namespace MediaBrowser.Providers.Manager
if (item != null)
{
// Try to throttle this a little bit.
- await Task.Delay(100).ConfigureAwait(false);
+ await Task.Delay(100, cancellationToken).ConfigureAwait(false);
var task = item is MusicArtist artist
? RefreshArtist(artist, refreshItem.Item2, cancellationToken)
@@ -1061,17 +1086,14 @@ namespace MediaBrowser.Providers.Manager
await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
// Collection folders don't validate their children so we'll have to simulate that here
-
- if (item is CollectionFolder collectionFolder)
+ switch (item)
{
- await RefreshCollectionFolderChildren(options, collectionFolder, cancellationToken).ConfigureAwait(false);
- }
- else
- {
- if (item is Folder folder)
- {
+ case CollectionFolder collectionFolder:
+ await RefreshCollectionFolderChildren(options, collectionFolder, cancellationToken).ConfigureAwait(false);
+ break;
+ case Folder folder:
await folder.ValidateChildren(new SimpleProgress<double>(), cancellationToken, options).ConfigureAwait(false);
- }
+ break;
}
}
@@ -1081,7 +1103,7 @@ namespace MediaBrowser.Providers.Manager
{
await child.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
- await child.ValidateChildren(new SimpleProgress<double>(), cancellationToken, options, true).ConfigureAwait(false);
+ await child.ValidateChildren(new SimpleProgress<double>(), cancellationToken, options).ConfigureAwait(false);
}
}
@@ -1117,12 +1139,13 @@ namespace MediaBrowser.Providers.Manager
}
}
+ /// <inheritdoc/>
public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
return RefreshItem(item, options, cancellationToken);
}
- private bool _disposed;
+ /// <inheritdoc/>
public void Dispose()
{
_disposed = true;
diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs
index 7f2a1410b..a4fd6ca84 100644
--- a/MediaBrowser.Providers/Manager/ProviderUtils.cs
+++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
@@ -26,6 +28,7 @@ namespace MediaBrowser.Providers.Manager
{
throw new ArgumentNullException(nameof(source));
}
+
if (target == null)
{
throw new ArgumentNullException(nameof(target));
@@ -111,7 +114,6 @@ namespace MediaBrowser.Providers.Manager
if (replaceData || targetResult.People == null || targetResult.People.Count == 0)
{
targetResult.People = sourceResult.People;
-
}
else if (targetResult.People != null && sourceResult.People != null)
{
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index 4d24a1ef5..42c7cec53 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -16,10 +16,10 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.5" />
- <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.1.5" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.6" />
+ <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.1.6" />
<PackageReference Include="OptimizedPriorityQueue" Version="4.2.0" />
- <PackageReference Include="PlaylistsNET" Version="1.0.4" />
+ <PackageReference Include="PlaylistsNET" Version="1.0.6" />
<PackageReference Include="TvDbSharper" Version="3.2.0" />
</ItemGroup>
@@ -27,6 +27,7 @@
<TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
+ <TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'" >true</TreatWarningsAsErrors>
</PropertyGroup>
<!-- Code Analyzers-->
diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
index 7023ef706..80acb2c05 100644
--- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -17,7 +19,7 @@ using MediaBrowser.Model.IO;
namespace MediaBrowser.Providers.MediaInfo
{
/// <summary>
- /// Uses ffmpeg to create video images
+ /// Uses ffmpeg to create video images.
/// </summary>
public class AudioImageProvider : IDynamicImageProvider
{
@@ -79,7 +81,6 @@ namespace MediaBrowser.Providers.MediaInfo
}
catch
{
-
}
}
@@ -130,6 +131,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
return false;
}
+
if (!item.IsFileProtocol)
{
return false;
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
index 3b6c8ff72..69c6fd722 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -19,7 +21,7 @@ using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Providers.MediaInfo
{
- class FFProbeAudioInfo
+ public class FFProbeAudioInfo
{
private readonly IMediaEncoder _mediaEncoder;
private readonly IItemRepository _itemRepo;
@@ -63,7 +65,6 @@ namespace MediaBrowser.Providers.MediaInfo
Path = path,
Protocol = protocol
}
-
}, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
@@ -91,8 +92,8 @@ namespace MediaBrowser.Providers.MediaInfo
audio.RunTimeTicks = mediaInfo.RunTimeTicks;
audio.Size = mediaInfo.Size;
- //var extension = (Path.GetExtension(audio.Path) ?? string.Empty).TrimStart('.');
- //audio.Container = extension;
+ // var extension = (Path.GetExtension(audio.Path) ?? string.Empty).TrimStart('.');
+ // audio.Container = extension;
FetchDataFromTags(audio, mediaInfo);
@@ -100,7 +101,7 @@ namespace MediaBrowser.Providers.MediaInfo
}
/// <summary>
- /// Fetches data from the tags dictionary
+ /// Fetches data from the tags dictionary.
/// </summary>
/// <param name="audio">The audio.</param>
/// <param name="data">The data.</param>
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
index 81103575d..4fabe709b 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.IO;
using System.Linq;
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
index 933cf03d6..53a6bb619 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
@@ -24,7 +24,6 @@ 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 MediaBrowser.Model.Providers;
using Microsoft.Extensions.Logging;
@@ -176,7 +175,7 @@ namespace MediaBrowser.Providers.MediaInfo
mediaAttachments = mediaInfo.MediaAttachments;
video.TotalBitrate = mediaInfo.Bitrate;
- //video.FormatName = (mediaInfo.Container ?? string.Empty)
+ // video.FormatName = (mediaInfo.Container ?? string.Empty)
// .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase);
// For dvd's this may not always be accurate, so don't set the runtime if the item already has one
@@ -283,7 +282,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
var video = (Video)item;
- //video.PlayableStreamFileNames = blurayInfo.Files.ToList();
+ // video.PlayableStreamFileNames = blurayInfo.Files.ToList();
// Use BD Info if it has multiple m2ts. Otherwise, treat it like a video file and rely more on ffprobe output
if (blurayInfo.Files.Length > 1)
@@ -342,7 +341,7 @@ namespace MediaBrowser.Providers.MediaInfo
}
/// <summary>
- /// Gets information about the longest playlist on a bdrom
+ /// Gets information about the longest playlist on a bdrom.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>VideoStream.</returns>
@@ -404,6 +403,7 @@ namespace MediaBrowser.Providers.MediaInfo
video.ProductionYear = data.ProductionYear;
}
}
+
if (data.PremiereDate.HasValue)
{
if (!video.PremiereDate.HasValue || isFullRefresh)
@@ -411,6 +411,7 @@ namespace MediaBrowser.Providers.MediaInfo
video.PremiereDate = data.PremiereDate;
}
}
+
if (data.IndexNumber.HasValue)
{
if (!video.IndexNumber.HasValue || isFullRefresh)
@@ -418,6 +419,7 @@ namespace MediaBrowser.Providers.MediaInfo
video.IndexNumber = data.IndexNumber;
}
}
+
if (data.ParentIndexNumber.HasValue)
{
if (!video.ParentIndexNumber.HasValue || isFullRefresh)
@@ -565,7 +567,7 @@ namespace MediaBrowser.Providers.MediaInfo
/// Creates dummy chapters.
/// </summary>
/// <param name="video">The video.</param>
- /// <return>An array of dummy chapters.</returns>
+ /// <returns>An array of dummy chapters.</returns>
private ChapterInfo[] CreateDummyChapters(Video video)
{
var runtime = video.RunTimeTicks ?? 0;
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs
index f35e82bb5..acddb73d0 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
index 2bbe8a968..64a5e7c8e 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.IO;
@@ -60,7 +62,6 @@ namespace MediaBrowser.Providers.MediaInfo
}
catch (IOException)
{
-
}
return streams;
@@ -189,9 +190,9 @@ namespace MediaBrowser.Providers.MediaInfo
filename = filename.Replace(" ", string.Empty);
// can't normalize this due to languages such as pt-br
- //filename = filename.Replace("-", string.Empty);
+ // filename = filename.Replace("-", string.Empty);
- //filename = filename.Replace(".", string.Empty);
+ // filename = filename.Replace(".", string.Empty);
return filename;
}
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
index fb87030ff..91ab7b4ac 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
index 08e503a71..e23854d90 100644
--- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
@@ -93,6 +95,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
videoIndex++;
}
+
if (mediaStream == imageStream)
{
break;
@@ -132,6 +135,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
return false;
}
+
if (!item.IsFileProtocol)
{
return false;
diff --git a/MediaBrowser.Providers/Movies/MovieExternalIds.cs b/MediaBrowser.Providers/Movies/MovieExternalIds.cs
index 1f07deacf..14080841c 100644
--- a/MediaBrowser.Providers/Movies/MovieExternalIds.cs
+++ b/MediaBrowser.Providers/Movies/MovieExternalIds.cs
@@ -1,21 +1,27 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Movies
{
public class ImdbExternalId : IExternalId
{
/// <inheritdoc />
- public string Name => "IMDb";
+ public string ProviderName => "IMDb";
/// <inheritdoc />
public string Key => MetadataProvider.Imdb.ToString();
/// <inheritdoc />
+ public ExternalIdMediaType? Type => null;
+
+ /// <inheritdoc />
public string UrlFormatString => "https://www.imdb.com/title/{0}";
/// <inheritdoc />
@@ -34,12 +40,15 @@ namespace MediaBrowser.Providers.Movies
public class ImdbPersonExternalId : IExternalId
{
/// <inheritdoc />
- public string Name => "IMDb";
+ public string ProviderName => "IMDb";
/// <inheritdoc />
public string Key => MetadataProvider.Imdb.ToString();
/// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Person;
+
+ /// <inheritdoc />
public string UrlFormatString => "https://www.imdb.com/name/{0}";
/// <inheritdoc />
diff --git a/MediaBrowser.Providers/Movies/MovieMetadataService.cs b/MediaBrowser.Providers/Movies/MovieMetadataService.cs
index 9faba4798..c477fb70f 100644
--- a/MediaBrowser.Providers/Movies/MovieMetadataService.cs
+++ b/MediaBrowser.Providers/Movies/MovieMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
@@ -28,10 +30,12 @@ namespace MediaBrowser.Providers.Movies
{
return false;
}
+
if (!item.ProductionYear.HasValue)
{
return false;
}
+
return base.IsFullLocalMetadata(item);
}
diff --git a/MediaBrowser.Providers/Movies/TrailerMetadataService.cs b/MediaBrowser.Providers/Movies/TrailerMetadataService.cs
index b45d2b745..f32d9ec0a 100644
--- a/MediaBrowser.Providers/Movies/TrailerMetadataService.cs
+++ b/MediaBrowser.Providers/Movies/TrailerMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -28,10 +30,12 @@ namespace MediaBrowser.Providers.Movies
{
return false;
}
+
if (!item.ProductionYear.HasValue)
{
return false;
}
+
return base.IsFullLocalMetadata(item);
}
diff --git a/MediaBrowser.Providers/Music/AlbumMetadataService.cs b/MediaBrowser.Providers/Music/AlbumMetadataService.cs
index 0c6f88c8b..8c9a1f59b 100644
--- a/MediaBrowser.Providers/Music/AlbumMetadataService.cs
+++ b/MediaBrowser.Providers/Music/AlbumMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/MediaBrowser.Providers/Music/ArtistMetadataService.cs b/MediaBrowser.Providers/Music/ArtistMetadataService.cs
index 4199c08c6..e29475dd7 100644
--- a/MediaBrowser.Providers/Music/ArtistMetadataService.cs
+++ b/MediaBrowser.Providers/Music/ArtistMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
diff --git a/MediaBrowser.Providers/Music/AudioMetadataService.cs b/MediaBrowser.Providers/Music/AudioMetadataService.cs
index 251cbbbec..8b9fc8a08 100644
--- a/MediaBrowser.Providers/Music/AudioMetadataService.cs
+++ b/MediaBrowser.Providers/Music/AudioMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
diff --git a/MediaBrowser.Providers/Music/Extensions.cs b/MediaBrowser.Providers/Music/Extensions.cs
index a1d5aa826..b57d35256 100644
--- a/MediaBrowser.Providers/Music/Extensions.cs
+++ b/MediaBrowser.Providers/Music/Extensions.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Linq;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
diff --git a/MediaBrowser.Providers/Music/MusicExternalIds.cs b/MediaBrowser.Providers/Music/MusicExternalIds.cs
index 628b9a9a1..a1726b996 100644
--- a/MediaBrowser.Providers/Music/MusicExternalIds.cs
+++ b/MediaBrowser.Providers/Music/MusicExternalIds.cs
@@ -1,18 +1,24 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Music
{
public class ImvdbId : IExternalId
{
/// <inheritdoc />
- public string Name => "IMVDb";
+ public string ProviderName => "IMVDb";
/// <inheritdoc />
public string Key => "IMVDb";
/// <inheritdoc />
+ public ExternalIdMediaType? Type => null;
+
+ /// <inheritdoc />
public string UrlFormatString => null;
/// <inheritdoc />
diff --git a/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs b/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs
index c586e7f70..1d611a746 100644
--- a/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs
+++ b/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
diff --git a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs
index c121eaa43..7dda7e9bf 100644
--- a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs
+++ b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
diff --git a/MediaBrowser.Providers/People/PersonMetadataService.cs b/MediaBrowser.Providers/People/PersonMetadataService.cs
index eeba9a56d..fe6d1d4d3 100644
--- a/MediaBrowser.Providers/People/PersonMetadataService.cs
+++ b/MediaBrowser.Providers/People/PersonMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
diff --git a/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs b/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs
index 0cae5d8fc..60ed96452 100644
--- a/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs
+++ b/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
diff --git a/MediaBrowser.Providers/Photos/PhotoMetadataService.cs b/MediaBrowser.Providers/Photos/PhotoMetadataService.cs
index 8cfae7e1a..cbbb433c0 100644
--- a/MediaBrowser.Providers/Photos/PhotoMetadataService.cs
+++ b/MediaBrowser.Providers/Photos/PhotoMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
diff --git a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs
index 9fc86b213..5cc0a527e 100644
--- a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs
+++ b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.IO;
@@ -61,18 +63,22 @@ namespace MediaBrowser.Providers.Playlists
{
return GetWplItems(stream);
}
+
if (string.Equals(".zpl", extension, StringComparison.OrdinalIgnoreCase))
{
return GetZplItems(stream);
}
+
if (string.Equals(".m3u", extension, StringComparison.OrdinalIgnoreCase))
{
return GetM3uItems(stream);
}
+
if (string.Equals(".m3u8", extension, StringComparison.OrdinalIgnoreCase))
{
return GetM3u8Items(stream);
}
+
if (string.Equals(".pls", extension, StringComparison.OrdinalIgnoreCase))
{
return GetPlsItems(stream);
@@ -95,7 +101,7 @@ namespace MediaBrowser.Providers.Playlists
private IEnumerable<LinkedChild> GetM3u8Items(Stream stream)
{
- var content = new M3u8Content();
+ var content = new M3uContent();
var playlist = content.GetFromStream(stream);
return playlist.PlaylistEntries.Select(i => new LinkedChild
diff --git a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs
index fcfc37bfe..5262919d5 100644
--- a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs
+++ b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs
index 3c314acb3..b211ed8b7 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
index b1a54f22f..7e54fcbdd 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -210,42 +212,79 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
public class Album
{
public string idAlbum { get; set; }
+
public string idArtist { get; set; }
+
public string strAlbum { get; set; }
+
public string strArtist { get; set; }
+
public string intYearReleased { get; set; }
+
public string strGenre { get; set; }
+
public string strSubGenre { get; set; }
+
public string strReleaseFormat { get; set; }
+
public string intSales { get; set; }
+
public string strAlbumThumb { get; set; }
+
public string strAlbumCDart { get; set; }
+
public string strDescriptionEN { get; set; }
+
public string strDescriptionDE { get; set; }
+
public string strDescriptionFR { get; set; }
+
public string strDescriptionCN { get; set; }
+
public string strDescriptionIT { get; set; }
+
public string strDescriptionJP { get; set; }
+
public string strDescriptionRU { get; set; }
+
public string strDescriptionES { get; set; }
+
public string strDescriptionPT { get; set; }
+
public string strDescriptionSE { get; set; }
+
public string strDescriptionNL { get; set; }
+
public string strDescriptionHU { get; set; }
+
public string strDescriptionNO { get; set; }
+
public string strDescriptionIL { get; set; }
+
public string strDescriptionPL { get; set; }
+
public object intLoved { get; set; }
+
public object intScore { get; set; }
+
public string strReview { get; set; }
+
public object strMood { get; set; }
+
public object strTheme { get; set; }
+
public object strSpeed { get; set; }
+
public object strLocation { get; set; }
+
public string strMusicBrainzID { get; set; }
+
public string strMusicBrainzArtistID { get; set; }
+
public object strItunesID { get; set; }
+
public object strAmazonID { get; set; }
+
public string strLocked { get; set; }
}
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
index 04cdab66a..243b62f7b 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
index d8a18a6bc..892f73422 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.IO;
@@ -85,7 +87,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
private void ProcessResult(MusicArtist item, Artist result, string preferredLanguage)
{
- //item.HomePageUrl = result.strWebsite;
+ // item.HomePageUrl = result.strWebsite;
if (!string.IsNullOrEmpty(result.strGenre))
{
@@ -199,45 +201,85 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
public class Artist
{
public string idArtist { get; set; }
+
public string strArtist { get; set; }
+
public string strArtistAlternate { get; set; }
+
public object idLabel { get; set; }
+
public string intFormedYear { get; set; }
+
public string intBornYear { get; set; }
+
public object intDiedYear { get; set; }
+
public object strDisbanded { get; set; }
+
public string strGenre { get; set; }
+
public string strSubGenre { get; set; }
+
public string strWebsite { get; set; }
+
public string strFacebook { get; set; }
+
public string strTwitter { get; set; }
+
public string strBiographyEN { get; set; }
+
public string strBiographyDE { get; set; }
+
public string strBiographyFR { get; set; }
+
public string strBiographyCN { get; set; }
+
public string strBiographyIT { get; set; }
+
public string strBiographyJP { get; set; }
+
public string strBiographyRU { get; set; }
+
public string strBiographyES { get; set; }
+
public string strBiographyPT { get; set; }
+
public string strBiographySE { get; set; }
+
public string strBiographyNL { get; set; }
+
public string strBiographyHU { get; set; }
+
public string strBiographyNO { get; set; }
+
public string strBiographyIL { get; set; }
+
public string strBiographyPL { get; set; }
+
public string strGender { get; set; }
+
public string intMembers { get; set; }
+
public string strCountry { get; set; }
+
public string strCountryCode { get; set; }
+
public string strArtistThumb { get; set; }
+
public string strArtistLogo { get; set; }
+
public string strArtistFanart { get; set; }
+
public string strArtistFanart2 { get; set; }
+
public string strArtistFanart3 { get; set; }
+
public string strArtistBanner { get; set; }
+
public string strMusicBrainzID { get; set; }
+
public object strLastFMChart { get; set; }
+
public string strLocked { get; set; }
}
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs
index ad3c7eb4b..9657a290f 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs
@@ -1,4 +1,6 @@
-using MediaBrowser.Model.Plugins;
+#pragma warning disable CS1591
+
+using MediaBrowser.Model.Plugins;
namespace MediaBrowser.Providers.Plugins.AudioDb
{
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs b/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs
index 478ea5190..1cc1f0fa1 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs
@@ -1,18 +1,24 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.AudioDb
{
public class AudioDbAlbumExternalId : IExternalId
{
/// <inheritdoc />
- public string Name => "TheAudioDb";
+ public string ProviderName => "TheAudioDb";
/// <inheritdoc />
public string Key => MetadataProvider.AudioDbAlbum.ToString();
/// <inheritdoc />
+ public ExternalIdMediaType? Type => null;
+
+ /// <inheritdoc />
public string UrlFormatString => "https://www.theaudiodb.com/album/{0}";
/// <inheritdoc />
@@ -22,12 +28,15 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
public class AudioDbOtherAlbumExternalId : IExternalId
{
/// <inheritdoc />
- public string Name => "TheAudioDb Album";
+ public string ProviderName => "TheAudioDb";
/// <inheritdoc />
public string Key => MetadataProvider.AudioDbAlbum.ToString();
/// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Album;
+
+ /// <inheritdoc />
public string UrlFormatString => "https://www.theaudiodb.com/album/{0}";
/// <inheritdoc />
@@ -37,12 +46,15 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
public class AudioDbArtistExternalId : IExternalId
{
/// <inheritdoc />
- public string Name => "TheAudioDb";
+ public string ProviderName => "TheAudioDb";
/// <inheritdoc />
public string Key => MetadataProvider.AudioDbArtist.ToString();
/// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Artist;
+
+ /// <inheritdoc />
public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}";
/// <inheritdoc />
@@ -52,12 +64,15 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
public class AudioDbOtherArtistExternalId : IExternalId
{
/// <inheritdoc />
- public string Name => "TheAudioDb Artist";
+ public string ProviderName => "TheAudioDb";
/// <inheritdoc />
public string Key => MetadataProvider.AudioDbArtist.ToString();
/// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist;
+
+ /// <inheritdoc />
public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}";
/// <inheritdoc />
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs
index 8532c4df3..cb7c0362e 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs
@@ -1,4 +1,6 @@
-using System;
+#pragma warning disable CS1591
+
+using System;
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs
index 0a2c7c124..9f36a03f9 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -361,6 +363,7 @@ namespace MediaBrowser.Providers.Music
return ParseReleaseList(subReader).ToList();
}
}
+
default:
{
reader.Skip();
@@ -396,6 +399,7 @@ namespace MediaBrowser.Providers.Music
reader.Read();
continue;
}
+
var releaseId = reader.GetAttribute("id");
using (var subReader = reader.ReadSubtree())
@@ -406,8 +410,10 @@ namespace MediaBrowser.Providers.Music
yield return release;
}
}
+
break;
}
+
default:
{
reader.Skip();
@@ -453,6 +459,7 @@ namespace MediaBrowser.Providers.Music
{
result.Year = date.Year;
}
+
break;
}
case "annotation":
@@ -480,6 +487,7 @@ namespace MediaBrowser.Providers.Music
break;
}
+
default:
{
reader.Skip();
@@ -518,6 +526,7 @@ namespace MediaBrowser.Providers.Music
return ParseArtistNameCredit(subReader);
}
}
+
default:
{
reader.Skip();
@@ -556,6 +565,7 @@ namespace MediaBrowser.Providers.Music
return ParseArtistArtistCredit(subReader, id);
}
}
+
default:
{
reader.Skip();
@@ -593,6 +603,7 @@ namespace MediaBrowser.Providers.Music
name = reader.ReadElementContentAsString();
break;
}
+
default:
{
reader.Skip();
@@ -680,11 +691,13 @@ namespace MediaBrowser.Providers.Music
reader.Read();
continue;
}
+
using (var subReader = reader.ReadSubtree())
{
return GetFirstReleaseGroupId(subReader);
}
}
+
default:
{
reader.Skip();
@@ -719,6 +732,7 @@ namespace MediaBrowser.Providers.Music
{
return reader.GetAttribute("id");
}
+
default:
{
reader.Skip();
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs
index 9d93dbdd1..955766403 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -108,11 +110,13 @@ namespace MediaBrowser.Providers.Music
reader.Read();
continue;
}
+
using (var subReader = reader.ReadSubtree())
{
return ParseArtistList(subReader).ToList();
}
}
+
default:
{
reader.Skip();
@@ -150,6 +154,7 @@ namespace MediaBrowser.Providers.Music
reader.Read();
continue;
}
+
var mbzId = reader.GetAttribute("id");
using (var subReader = reader.ReadSubtree())
@@ -160,8 +165,10 @@ namespace MediaBrowser.Providers.Music
yield return artist;
}
}
+
break;
}
+
default:
{
reader.Skip();
@@ -202,6 +209,7 @@ namespace MediaBrowser.Providers.Music
result.Overview = reader.ReadElementContentAsString();
break;
}
+
default:
{
// there is sort-name if ever needed
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs
index 5843b0c7d..980da9a01 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs
@@ -1,4 +1,6 @@
-using MediaBrowser.Model.Plugins;
+#pragma warning disable CS1591
+
+using MediaBrowser.Model.Plugins;
namespace MediaBrowser.Providers.Plugins.MusicBrainz
{
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs
index 3be6f570b..5600c389c 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs
@@ -1,6 +1,9 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
using MediaBrowser.Providers.Plugins.MusicBrainz;
namespace MediaBrowser.Providers.Music
@@ -8,12 +11,15 @@ namespace MediaBrowser.Providers.Music
public class MusicBrainzReleaseGroupExternalId : IExternalId
{
/// <inheritdoc />
- public string Name => "MusicBrainz Release Group";
+ public string ProviderName => "MusicBrainz";
/// <inheritdoc />
public string Key => MetadataProvider.MusicBrainzReleaseGroup.ToString();
/// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup;
+
+ /// <inheritdoc />
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}";
/// <inheritdoc />
@@ -23,12 +29,15 @@ namespace MediaBrowser.Providers.Music
public class MusicBrainzAlbumArtistExternalId : IExternalId
{
/// <inheritdoc />
- public string Name => "MusicBrainz Album Artist";
+ public string ProviderName => "MusicBrainz";
/// <inheritdoc />
public string Key => MetadataProvider.MusicBrainzAlbumArtist.ToString();
/// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.AlbumArtist;
+
+ /// <inheritdoc />
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
/// <inheritdoc />
@@ -38,12 +47,15 @@ namespace MediaBrowser.Providers.Music
public class MusicBrainzAlbumExternalId : IExternalId
{
/// <inheritdoc />
- public string Name => "MusicBrainz Album";
+ public string ProviderName => "MusicBrainz";
/// <inheritdoc />
public string Key => MetadataProvider.MusicBrainzAlbum.ToString();
/// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Album;
+
+ /// <inheritdoc />
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}";
/// <inheritdoc />
@@ -53,12 +65,15 @@ namespace MediaBrowser.Providers.Music
public class MusicBrainzArtistExternalId : IExternalId
{
/// <inheritdoc />
- public string Name => "MusicBrainz";
+ public string ProviderName => "MusicBrainz";
/// <inheritdoc />
public string Key => MetadataProvider.MusicBrainzArtist.ToString();
/// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Artist;
+
+ /// <inheritdoc />
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
/// <inheritdoc />
@@ -68,13 +83,16 @@ namespace MediaBrowser.Providers.Music
public class MusicBrainzOtherArtistExternalId : IExternalId
{
/// <inheritdoc />
- public string Name => "MusicBrainz Artist";
+ public string ProviderName => "MusicBrainz";
/// <inheritdoc />
public string Key => MetadataProvider.MusicBrainzArtist.ToString();
/// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist;
+
+ /// <inheritdoc />
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
/// <inheritdoc />
@@ -84,12 +102,15 @@ namespace MediaBrowser.Providers.Music
public class MusicBrainzTrackId : IExternalId
{
/// <inheritdoc />
- public string Name => "MusicBrainz Track";
+ public string ProviderName => "MusicBrainz";
/// <inheritdoc />
public string Key => MetadataProvider.MusicBrainzTrack.ToString();
/// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Track;
+
+ /// <inheritdoc />
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}";
/// <inheritdoc />
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
index 8e1b3ea37..a7e6267da 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
@@ -1,4 +1,6 @@
-using System;
+#pragma warning disable CS1591
+
+using System;
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
diff --git a/MediaBrowser.Providers/Plugins/Omdb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/Omdb/Configuration/PluginConfiguration.cs
index a9eecdd9e..196f14e7c 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/Configuration/PluginConfiguration.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/Configuration/PluginConfiguration.cs
@@ -1,4 +1,6 @@
-using MediaBrowser.Model.Plugins;
+#pragma warning disable CS1591
+
+using MediaBrowser.Model.Plugins;
namespace MediaBrowser.Providers.Plugins.Omdb
{
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
index b074a1b0a..50d6b78ae 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs
index d78a37784..2d09a66c3 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
index 4a29ba4d0..944ba26af 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -286,27 +288,49 @@ namespace MediaBrowser.Providers.Plugins.Omdb
class SearchResult
{
public string Title { get; set; }
+
public string Year { get; set; }
+
public string Rated { get; set; }
+
public string Released { get; set; }
+
public string Season { get; set; }
+
public string Episode { get; set; }
+
public string Runtime { get; set; }
+
public string Genre { get; set; }
+
public string Director { get; set; }
+
public string Writer { get; set; }
+
public string Actors { get; set; }
+
public string Plot { get; set; }
+
public string Language { get; set; }
+
public string Country { get; set; }
+
public string Awards { get; set; }
+
public string Poster { get; set; }
+
public string Metascore { get; set; }
+
public string imdbRating { get; set; }
+
public string imdbVotes { get; set; }
+
public string imdbID { get; set; }
+
public string seriesID { get; set; }
+
public string Type { get; set; }
+
public string Response { get; set; }
}
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
index 19b4bd1e3..9700f3b18 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -77,7 +79,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
&& int.TryParse(result.imdbVotes, NumberStyles.Number, _usCulture, out var voteCount)
&& voteCount >= 0)
{
- //item.VoteCount = voteCount;
+ // item.VoteCount = voteCount;
}
if (!string.IsNullOrEmpty(result.imdbRating)
@@ -178,7 +180,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
&& int.TryParse(result.imdbVotes, NumberStyles.Number, _usCulture, out var voteCount)
&& voteCount >= 0)
{
- //item.VoteCount = voteCount;
+ // item.VoteCount = voteCount;
}
if (!string.IsNullOrEmpty(result.imdbRating)
diff --git a/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs b/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs
index 6ce2333e0..4cf5f4ce6 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs
@@ -1,4 +1,6 @@
-using System;
+#pragma warning disable CS1591
+
+using System;
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/TheTvdb/Configuration/PluginConfiguration.cs
index 0a73634dc..690a52c4d 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/Configuration/PluginConfiguration.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/Configuration/PluginConfiguration.cs
@@ -1,4 +1,6 @@
-using MediaBrowser.Model.Plugins;
+#pragma warning disable CS1591
+
+using MediaBrowser.Model.Plugins;
namespace MediaBrowser.Providers.Plugins.TheTvdb
{
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs b/MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs
index 2e6f548ca..aa5f819f0 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs
@@ -1,4 +1,6 @@
-using System;
+#pragma warning disable CS1591
+
+using System;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Serialization;
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs
index 38e887be1..cd2f96f14 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs
@@ -1,7 +1,10 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
+using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Providers;
@@ -227,6 +230,45 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
return GetEpisodesPageAsync(tvdbId, 1, episodeQuery, language, cancellationToken);
}
+ public async IAsyncEnumerable<KeyType> GetImageKeyTypesForSeriesAsync(int tvdbId, string language, [EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ var cacheKey = GenerateKey(nameof(TvDbClient.Series.GetImagesSummaryAsync), tvdbId);
+ var imagesSummary = await TryGetValue(cacheKey, language, () => TvDbClient.Series.GetImagesSummaryAsync(tvdbId, cancellationToken)).ConfigureAwait(false);
+
+ if (imagesSummary.Data.Fanart > 0)
+ {
+ yield return KeyType.Fanart;
+ }
+
+ if (imagesSummary.Data.Series > 0)
+ {
+ yield return KeyType.Series;
+ }
+
+ if (imagesSummary.Data.Poster > 0)
+ {
+ yield return KeyType.Poster;
+ }
+ }
+
+ public async IAsyncEnumerable<KeyType> GetImageKeyTypesForSeasonAsync(int tvdbId, string language, [EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ var cacheKey = GenerateKey(nameof(TvDbClient.Series.GetImagesSummaryAsync), tvdbId);
+ var imagesSummary = await TryGetValue(cacheKey, language, () => TvDbClient.Series.GetImagesSummaryAsync(tvdbId, cancellationToken)).ConfigureAwait(false);
+
+ if (imagesSummary.Data.Season > 0)
+ {
+ yield return KeyType.Season;
+ }
+
+ if (imagesSummary.Data.Fanart > 0)
+ {
+ yield return KeyType.Fanart;
+ }
+
+ // TODO seasonwide is not supported in TvDbSharper
+ }
+
private async Task<T> TryGetValue<T>(string key, string language, Func<Task<T>> resultFactory)
{
if (_cache.TryGetValue(key, out T cachedValue))
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs
index 1a4c78538..9b87e3617 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Threading;
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs
index b4876c24e..ced287d54 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Threading;
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs
index 9a44573ed..9db21f012 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs
index 7bd815f76..e9ba20475 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
@@ -63,8 +65,8 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
var language = item.GetPreferredMetadataLanguage();
var remoteImages = new List<RemoteImageInfo>();
- var keyTypes = new[] { KeyType.Season, KeyType.Seasonwide, KeyType.Fanart };
- foreach (var keyType in keyTypes)
+ var keyTypes = _tvdbClientManager.GetImageKeyTypesForSeasonAsync(tvdbId, language, cancellationToken).ConfigureAwait(false);
+ await foreach (var keyType in keyTypes)
{
var imageQuery = new ImagesQuery
{
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs
index 50f66945f..c33aefbc1 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
@@ -57,9 +59,10 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
var language = item.GetPreferredMetadataLanguage();
var remoteImages = new List<RemoteImageInfo>();
- var keyTypes = new[] { KeyType.Poster, KeyType.Series, KeyType.Fanart };
var tvdbId = Convert.ToInt32(item.GetProviderId(MetadataProvider.Tvdb));
- foreach (KeyType keyType in keyTypes)
+ var allowedKeyTypes = _tvdbClientManager.GetImageKeyTypesForSeriesAsync(tvdbId, language, cancellationToken)
+ .ConfigureAwait(false);
+ await foreach (KeyType keyType in allowedKeyTypes)
{
var imageQuery = new ImagesQuery
{
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs
index c9edcc8e9..df48629d0 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
@@ -245,11 +247,15 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
{
Name = tvdbTitles.FirstOrDefault(),
ProductionYear = firstAired.Year,
- SearchProviderName = Name,
- ImageUrl = TvdbUtils.BannerUrl + seriesSearchResult.Banner
-
+ SearchProviderName = Name
};
+ if (!string.IsNullOrEmpty(seriesSearchResult.Banner))
+ {
+ // Results from their Search endpoints already include the /banners/ part in the url, because reasons...
+ remoteSearchResult.ImageUrl = TvdbUtils.TvdbImageBaseUrl + seriesSearchResult.Banner;
+ }
+
try
{
var seriesSesult =
@@ -364,10 +370,14 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
Type = PersonType.Actor,
Name = (actor.Name ?? string.Empty).Trim(),
Role = actor.Role,
- ImageUrl = TvdbUtils.BannerUrl + actor.Image,
SortOrder = actor.SortOrder
};
+ if (!string.IsNullOrEmpty(actor.Image))
+ {
+ personInfo.ImageUrl = TvdbUtils.BannerUrl + actor.Image;
+ }
+
if (!string.IsNullOrWhiteSpace(personInfo.Name))
{
result.AddPerson(personInfo);
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs
index 79d879aa1..37a8d04a6 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Entities;
@@ -7,7 +9,8 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
{
public const string TvdbApiKey = "OG4V3YJ3FAP7FP2K";
public const string TvdbBaseUrl = "https://www.thetvdb.com/";
- public const string BannerUrl = TvdbBaseUrl + "banners/";
+ public const string TvdbImageBaseUrl = "https://www.thetvdb.com";
+ public const string BannerUrl = TvdbImageBaseUrl + "/banners/";
public static ImageType GetImageTypeFromKeyType(string keyType)
{
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs
index ad0851cef..1f7ec6433 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs
@@ -2,18 +2,25 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
{
+ /// <summary>
+ /// External ID for a TMDB box set.
+ /// </summary>
public class TmdbBoxSetExternalId : IExternalId
{
/// <inheritdoc />
- public string Name => TmdbUtils.ProviderName;
+ public string ProviderName => TmdbUtils.ProviderName;
/// <inheritdoc />
public string Key => MetadataProvider.TmdbCollection.ToString();
/// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.BoxSet;
+
+ /// <inheritdoc />
public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "collection/{0}";
/// <inheritdoc />
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
index 23eb00b5c..c41bd925e 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
index 15f0a9004..20b6cd505 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -159,7 +161,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
{
var mainResult = await FetchMainResult(tmdbId, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
- if (mainResult == null) return;
+ if (mainResult == null)
+ {
+ return;
+ }
var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, preferredMetadataLanguage);
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionImages.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionImages.cs
index 2410ca16b..0a8994d54 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionImages.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionImages.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
@@ -6,6 +8,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Collections
public class CollectionImages
{
public List<Backdrop> Backdrops { get; set; }
+
public List<Poster> Posters { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionResult.cs
index 3437552df..c6b851c23 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionResult.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionResult.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Collections
@@ -5,11 +7,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Collections
public class CollectionResult
{
public int Id { get; set; }
+
public string Name { get; set; }
+
public string Overview { get; set; }
+
public string Poster_Path { get; set; }
+
public string Backdrop_Path { get; set; }
+
public List<Part> Parts { get; set; }
+
public CollectionImages Images { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/Part.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/Part.cs
index 462fdab53..a48124b3e 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/Part.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/Part.cs
@@ -1,11 +1,17 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Collections
{
public class Part
{
public string Title { get; set; }
+
public int Id { get; set; }
+
public string Release_Date { get; set; }
+
public string Poster_Path { get; set; }
+
public string Backdrop_Path { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Backdrop.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Backdrop.cs
index 35e3e2112..5b7627f6e 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Backdrop.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Backdrop.cs
@@ -1,13 +1,21 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
{
public class Backdrop
{
public double Aspect_Ratio { get; set; }
+
public string File_Path { get; set; }
+
public int Height { get; set; }
+
public string Iso_639_1 { get; set; }
+
public double Vote_Average { get; set; }
+
public int Vote_Count { get; set; }
+
public int Width { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Crew.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Crew.cs
index 6a5e74ddb..339ecb628 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Crew.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Crew.cs
@@ -1,12 +1,19 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
{
public class Crew
{
public int Id { get; set; }
+
public string Credit_Id { get; set; }
+
public string Name { get; set; }
+
public string Department { get; set; }
+
public string Job { get; set; }
+
public string Profile_Path { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/ExternalIds.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/ExternalIds.cs
index a083f6e9c..310c871ec 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/ExternalIds.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/ExternalIds.cs
@@ -1,11 +1,17 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
{
public class ExternalIds
{
public string Imdb_Id { get; set; }
+
public object Freebase_Id { get; set; }
+
public string Freebase_Mid { get; set; }
+
public int Tvdb_Id { get; set; }
+
public int Tvrage_Id { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Genre.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Genre.cs
index 7f1a394c3..9ba1c15c6 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Genre.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Genre.cs
@@ -1,8 +1,11 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
{
public class Genre
{
public int Id { get; set; }
+
public string Name { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Images.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Images.cs
index 166f9b740..0538cf174 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Images.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Images.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
@@ -5,6 +7,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
public class Images
{
public List<Backdrop> Backdrops { get; set; }
+
public List<Poster> Posters { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keyword.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keyword.cs
index 72f417be5..fff86931b 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keyword.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keyword.cs
@@ -1,8 +1,11 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
{
public class Keyword
{
public int Id { get; set; }
+
public string Name { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keywords.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keywords.cs
index ec2d7a035..235ecb568 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keywords.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keywords.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Poster.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Poster.cs
index 0cf04a6ce..4f61e978b 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Poster.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Poster.cs
@@ -1,13 +1,21 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
{
public class Poster
{
public double Aspect_Ratio { get; set; }
+
public string File_Path { get; set; }
+
public int Height { get; set; }
+
public string Iso_639_1 { get; set; }
+
public double Vote_Average { get; set; }
+
public int Vote_Count { get; set; }
+
public int Width { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Profile.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Profile.cs
index b45cfc30f..0a1f8843e 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Profile.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Profile.cs
@@ -1,11 +1,17 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
{
public class Profile
{
public string File_Path { get; set; }
+
public int Width { get; set; }
+
public int Height { get; set; }
+
public object Iso_639_1 { get; set; }
+
public double Aspect_Ratio { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Still.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Still.cs
index 9fc82cfee..61de819b9 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Still.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Still.cs
@@ -1,14 +1,23 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
{
public class Still
{
public double Aspect_Ratio { get; set; }
+
public string File_Path { get; set; }
+
public int Height { get; set; }
+
public string Id { get; set; }
+
public string Iso_639_1 { get; set; }
+
public double Vote_Average { get; set; }
+
public int Vote_Count { get; set; }
+
public int Width { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/StillImages.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/StillImages.cs
index 23af4b697..59ab18b7b 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/StillImages.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/StillImages.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Video.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Video.cs
index 19bfd62f6..ebd5c7ace 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Video.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Video.cs
@@ -1,14 +1,23 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
{
public class Video
{
public string Id { get; set; }
+
public string Iso_639_1 { get; set; }
+
public string Iso_3166_1 { get; set; }
+
public string Key { get; set; }
+
public string Name { get; set; }
+
public string Site { get; set; }
+
public string Size { get; set; }
+
public string Type { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Videos.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Videos.cs
index 26e839de7..241dcab4d 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Videos.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Videos.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/BelongsToCollection.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/BelongsToCollection.cs
index aaca57f05..e8745be14 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/BelongsToCollection.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/BelongsToCollection.cs
@@ -1,10 +1,15 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
{
public class BelongsToCollection
{
public int Id { get; set; }
+
public string Name { get; set; }
+
public string Poster_Path { get; set; }
+
public string Backdrop_Path { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Cast.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Cast.cs
index d70f218aa..937cfb8f6 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Cast.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Cast.cs
@@ -1,12 +1,19 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
{
public class Cast
{
public int Id { get; set; }
+
public string Name { get; set; }
+
public string Character { get; set; }
+
public int Order { get; set; }
+
public int Cast_Id { get; set; }
+
public string Profile_Path { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Casts.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Casts.cs
index c41699bc7..37547640f 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Casts.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Casts.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
@@ -6,6 +8,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
public class Casts
{
public List<Cast> Cast { get; set; }
+
public List<Crew> Crew { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Country.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Country.cs
index 71d1f7c24..edd656a46 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Country.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Country.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
@@ -5,7 +7,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
public class Country
{
public string Iso_3166_1 { get; set; }
+
public string Certification { get; set; }
+
public DateTime Release_Date { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/MovieResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/MovieResult.cs
index 2a9b9779a..7566df8b6 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/MovieResult.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/MovieResult.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
@@ -6,34 +8,63 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
public class MovieResult
{
public bool Adult { get; set; }
+
public string Backdrop_Path { get; set; }
+
public BelongsToCollection Belongs_To_Collection { get; set; }
+
public int Budget { get; set; }
+
public List<Genre> Genres { get; set; }
+
public string Homepage { get; set; }
+
public int Id { get; set; }
+
public string Imdb_Id { get; set; }
+
public string Original_Title { get; set; }
+
public string Original_Name { get; set; }
+
public string Overview { get; set; }
+
public double Popularity { get; set; }
+
public string Poster_Path { get; set; }
+
public List<ProductionCompany> Production_Companies { get; set; }
+
public List<ProductionCountry> Production_Countries { get; set; }
+
public string Release_Date { get; set; }
+
public int Revenue { get; set; }
+
public int Runtime { get; set; }
+
public List<SpokenLanguage> Spoken_Languages { get; set; }
+
public string Status { get; set; }
+
public string Tagline { get; set; }
+
public string Title { get; set; }
+
public string Name { get; set; }
+
public double Vote_Average { get; set; }
+
public int Vote_Count { get; set; }
+
public Casts Casts { get; set; }
+
public Releases Releases { get; set; }
+
public Images Images { get; set; }
+
public Keywords Keywords { get; set; }
+
public Trailers Trailers { get; set; }
public string GetOriginalTitle()
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCompany.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCompany.cs
index 11158ade5..2788731b2 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCompany.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCompany.cs
@@ -1,8 +1,11 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
{
public class ProductionCompany
{
public string Name { get; set; }
+
public int Id { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCountry.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCountry.cs
index 43d00fe7a..1b6f2cc67 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCountry.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCountry.cs
@@ -1,8 +1,11 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
{
public class ProductionCountry
{
public string Iso_3166_1 { get; set; }
+
public string Name { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Releases.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Releases.cs
index d35111dc4..276fbaaf5 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Releases.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Releases.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/SpokenLanguage.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/SpokenLanguage.cs
index 41defa9d0..67231d219 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/SpokenLanguage.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/SpokenLanguage.cs
@@ -1,8 +1,11 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
{
public class SpokenLanguage
{
public string Iso_639_1 { get; set; }
+
public string Name { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Trailers.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Trailers.cs
index bdc40b483..166860f51 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Trailers.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Trailers.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Youtube.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Youtube.cs
index 6be4ef5b5..6885b7dab 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Youtube.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Youtube.cs
@@ -1,9 +1,13 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
{
public class Youtube
{
public string Name { get; set; }
+
public string Size { get; set; }
+
public string Source { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonImages.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonImages.cs
index 59423c7bc..3ea12334e 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonImages.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonImages.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonResult.cs
index 50c47eefd..460ced49a 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonResult.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonResult.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
@@ -6,18 +8,31 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.People
public class PersonResult
{
public bool Adult { get; set; }
+
public List<string> Also_Known_As { get; set; }
+
public string Biography { get; set; }
+
public string Birthday { get; set; }
+
public string Deathday { get; set; }
+
public string Homepage { get; set; }
+
public int Id { get; set; }
+
public string Imdb_Id { get; set; }
+
public string Name { get; set; }
+
public string Place_Of_Birth { get; set; }
+
public double Popularity { get; set; }
+
public string Profile_Path { get; set; }
+
public PersonImages Images { get; set; }
+
public ExternalIds External_Ids { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/ExternalIdLookupResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/ExternalIdLookupResult.cs
index 62b12aa97..87c2a723d 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/ExternalIdLookupResult.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/ExternalIdLookupResult.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Search
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/MovieResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/MovieResult.cs
index 51c26a61c..401c75c31 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/MovieResult.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/MovieResult.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Search
{
public class MovieResult
@@ -7,55 +9,66 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Search
/// </summary>
/// <value><c>true</c> if adult; otherwise, <c>false</c>.</value>
public bool Adult { get; set; }
+
/// <summary>
/// Gets or sets the backdrop_path.
/// </summary>
/// <value>The backdrop_path.</value>
public string Backdrop_Path { get; set; }
+
/// <summary>
/// Gets or sets the id.
/// </summary>
/// <value>The id.</value>
public int Id { get; set; }
+
/// <summary>
/// Gets or sets the original_title.
/// </summary>
/// <value>The original_title.</value>
public string Original_Title { get; set; }
+
/// <summary>
/// Gets or sets the original_name.
/// </summary>
/// <value>The original_name.</value>
public string Original_Name { get; set; }
+
/// <summary>
/// Gets or sets the release_date.
/// </summary>
/// <value>The release_date.</value>
public string Release_Date { get; set; }
+
/// <summary>
/// Gets or sets the poster_path.
/// </summary>
/// <value>The poster_path.</value>
public string Poster_Path { get; set; }
+
/// <summary>
/// Gets or sets the popularity.
/// </summary>
/// <value>The popularity.</value>
public double Popularity { get; set; }
+
/// <summary>
/// Gets or sets the title.
/// </summary>
/// <value>The title.</value>
public string Title { get; set; }
+
/// <summary>
/// Gets or sets the vote_average.
/// </summary>
/// <value>The vote_average.</value>
public double Vote_Average { get; set; }
+
/// <summary>
- /// For collection search results
+ /// For collection search results.
/// </summary>
public string Name { get; set; }
+
/// <summary>
/// Gets or sets the vote_count.
/// </summary>
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/PersonSearchResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/PersonSearchResult.cs
index c3ad7253a..4cff45ca6 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/PersonSearchResult.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/PersonSearchResult.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Search
{
public class PersonSearchResult
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TmdbSearchResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TmdbSearchResult.cs
index 7a33acbc7..3b9257b62 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TmdbSearchResult.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TmdbSearchResult.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Search
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TvResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TvResult.cs
index b7fbd294c..b2bb068b5 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TvResult.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TvResult.cs
@@ -1,15 +1,25 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Search
{
public class TvResult
{
public string Backdrop_Path { get; set; }
+
public string First_Air_Date { get; set; }
+
public int Id { get; set; }
+
public string Original_Name { get; set; }
+
public string Poster_Path { get; set; }
+
public double Popularity { get; set; }
+
public string Name { get; set; }
+
public double Vote_Average { get; set; }
+
public int Vote_Count { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Cast.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Cast.cs
index 9c770545c..4ce26c65e 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Cast.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Cast.cs
@@ -1,12 +1,19 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
{
public class Cast
{
public string Character { get; set; }
+
public string Credit_Id { get; set; }
+
public int Id { get; set; }
+
public string Name { get; set; }
+
public string Profile_Path { get; set; }
+
public int Order { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRating.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRating.cs
index bccb234e7..aef4e2863 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRating.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRating.cs
@@ -1,8 +1,11 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
{
public class ContentRating
{
public string Iso_3166_1 { get; set; }
+
public string Rating { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRatings.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRatings.cs
index 360c20c66..ae1b5668d 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRatings.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRatings.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/CreatedBy.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/CreatedBy.cs
index 35e8eaecb..ba36632e0 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/CreatedBy.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/CreatedBy.cs
@@ -1,9 +1,13 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
{
public class CreatedBy
{
public int Id { get; set; }
+
public string Name { get; set; }
+
public string Profile_Path { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Credits.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Credits.cs
index ebf412c2d..47205d875 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Credits.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Credits.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
@@ -6,6 +8,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
public class Credits
{
public List<Cast> Cast { get; set; }
+
public List<Crew> Crew { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Episode.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Episode.cs
index 8203632b7..53e3c2695 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Episode.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Episode.cs
@@ -1,14 +1,23 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
{
public class Episode
{
public string Air_Date { get; set; }
+
public int Episode_Number { get; set; }
+
public int Id { get; set; }
+
public string Name { get; set; }
+
public string Overview { get; set; }
+
public string Still_Path { get; set; }
+
public double Vote_Average { get; set; }
+
public int Vote_Count { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeCredits.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeCredits.cs
index f89859f85..9707e4bf4 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeCredits.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeCredits.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
@@ -6,7 +8,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
public class EpisodeCredits
{
public List<Cast> Cast { get; set; }
+
public List<Crew> Crew { get; set; }
+
public List<GuestStar> Guest_Stars { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeResult.cs
index e25b65d70..4458bad36 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeResult.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeResult.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
@@ -6,18 +8,31 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
public class EpisodeResult
{
public DateTime Air_Date { get; set; }
+
public int Episode_Number { get; set; }
+
public string Name { get; set; }
+
public string Overview { get; set; }
+
public int Id { get; set; }
+
public object Production_Code { get; set; }
+
public int Season_Number { get; set; }
+
public string Still_Path { get; set; }
+
public double Vote_Average { get; set; }
+
public int Vote_Count { get; set; }
+
public StillImages Images { get; set; }
+
public ExternalIds External_Ids { get; set; }
+
public EpisodeCredits Credits { get; set; }
+
public Tmdb.Models.General.Videos Videos { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/GuestStar.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/GuestStar.cs
index 260f3f610..8f3988641 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/GuestStar.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/GuestStar.cs
@@ -1,12 +1,19 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
{
public class GuestStar
{
public int Id { get; set; }
+
public string Name { get; set; }
+
public string Credit_Id { get; set; }
+
public string Character { get; set; }
+
public int Order { get; set; }
+
public string Profile_Path { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Network.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Network.cs
index 5ed310827..3dc310d33 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Network.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Network.cs
@@ -1,8 +1,11 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
{
public class Network
{
public int Id { get; set; }
+
public string Name { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Season.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Season.cs
index fddf950ee..9cbd283a9 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Season.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Season.cs
@@ -1,11 +1,17 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
{
public class Season
{
public string Air_Date { get; set; }
+
public int Episode_Count { get; set; }
+
public int Id { get; set; }
+
public string Poster_Path { get; set; }
+
public int Season_Number { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonImages.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonImages.cs
index 13f6d57c8..f364d4921 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonImages.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonImages.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonResult.cs
index 13b4c30f8..e98048eac 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonResult.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonResult.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
@@ -7,15 +9,25 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
public class SeasonResult
{
public DateTime Air_Date { get; set; }
+
public List<Episode> Episodes { get; set; }
+
public string Name { get; set; }
+
public string Overview { get; set; }
+
public int Id { get; set; }
+
public string Poster_Path { get; set; }
+
public int Season_Number { get; set; }
+
public Credits Credits { get; set; }
+
public SeasonImages Images { get; set; }
+
public ExternalIds External_Ids { get; set; }
+
public General.Videos Videos { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeriesResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeriesResult.cs
index 5c1666c77..331cd59fa 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeriesResult.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeriesResult.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
@@ -7,34 +9,63 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
public class SeriesResult
{
public string Backdrop_Path { get; set; }
+
public List<CreatedBy> Created_By { get; set; }
+
public List<int> Episode_Run_Time { get; set; }
+
public DateTime First_Air_Date { get; set; }
+
public List<Genre> Genres { get; set; }
+
public string Homepage { get; set; }
+
public int Id { get; set; }
+
public bool In_Production { get; set; }
+
public List<string> Languages { get; set; }
+
public DateTime Last_Air_Date { get; set; }
+
public string Name { get; set; }
+
public List<Network> Networks { get; set; }
+
public int Number_Of_Episodes { get; set; }
+
public int Number_Of_Seasons { get; set; }
+
public string Original_Name { get; set; }
+
public List<string> Origin_Country { get; set; }
+
public string Overview { get; set; }
+
public string Popularity { get; set; }
+
public string Poster_Path { get; set; }
+
public List<Season> Seasons { get; set; }
+
public string Status { get; set; }
+
public double Vote_Average { get; set; }
+
public int Vote_Count { get; set; }
+
public Credits Credits { get; set; }
+
public Images Images { get; set; }
+
public Keywords Keywords { get; set; }
+
public ExternalIds External_Ids { get; set; }
+
public General.Videos Videos { get; set; }
+
public ContentRatings Content_Ratings { get; set; }
+
public string ResultLanguage { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/GenericTmdbMovieInfo.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/GenericTmdbMovieInfo.cs
index 60f37dc17..27ca3759e 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/GenericTmdbMovieInfo.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/GenericTmdbMovieInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -131,7 +133,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
movie.Overview = string.IsNullOrWhiteSpace(movieData.Overview) ? null : WebUtility.HtmlDecode(movieData.Overview);
movie.Overview = movie.Overview != null ? movie.Overview.Replace("\n\n", "\n") : null;
- //movie.HomePageUrl = movieData.homepage;
+ // movie.HomePageUrl = movieData.homepage;
if (!string.IsNullOrEmpty(movieData.Tagline))
{
@@ -167,7 +169,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
movie.CommunityRating = rating;
}
- //movie.VoteCount = movieData.vote_count;
+ // movie.VoteCount = movieData.vote_count;
if (movieData.Releases != null && movieData.Releases.Countries != null)
{
@@ -201,7 +203,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
}
}
- //studios
+ // studios
if (movieData.Production_Companies != null)
{
movie.SetStudios(movieData.Production_Companies.Select(c => c.Name));
@@ -219,8 +221,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
resultItem.ResetPeople();
var tmdbImageUrl = settings.images.GetImageUrl("original");
- //Actors, Directors, Writers - all in People
- //actors come from cast
+ // Actors, Directors, Writers - all in People
+ // actors come from cast
if (movieData.Casts != null && movieData.Casts.Cast != null)
{
foreach (var actor in movieData.Casts.Cast.OrderBy(a => a.Order))
@@ -247,7 +249,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
}
}
- //and the rest from crew
+ // and the rest from crew
if (movieData.Casts?.Crew != null)
{
var keepTypes = new[]
@@ -289,7 +291,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
}
}
- //if (movieData.keywords != null && movieData.keywords.keywords != null)
+ // if (movieData.keywords != null && movieData.keywords.keywords != null)
//{
// movie.Keywords = movieData.keywords.keywords.Select(i => i.name).ToList();
//}
@@ -304,6 +306,5 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
}).ToArray();
}
}
-
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs
index a11c89459..36a06fba7 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -107,6 +109,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
{
return 3;
}
+
if (!isLanguageEn)
{
if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
@@ -114,10 +117,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
return 2;
}
}
+
if (string.IsNullOrEmpty(i.Language))
{
return isLanguageEn ? 3 : 2;
}
+
return 0;
})
.ThenByDescending(i => i.CommunityRating ?? 0)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs
index 7aec27e97..9610e4058 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs
@@ -3,18 +3,25 @@ using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
{
+ /// <summary>
+ /// External ID for a TMBD movie.
+ /// </summary>
public class TmdbMovieExternalId : IExternalId
{
/// <inheritdoc />
- public string Name => TmdbUtils.ProviderName;
+ public string ProviderName => TmdbUtils.ProviderName;
/// <inheritdoc />
public string Key => MetadataProvider.Tmdb.ToString();
/// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Movie;
+
+ /// <inheritdoc />
public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "movie/{0}";
/// <inheritdoc />
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
index 64d3ecd7b..74870c999 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -25,7 +27,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
{
/// <summary>
- /// Class MovieDbProvider
+ /// Class MovieDbProvider.
/// </summary>
public class TmdbMovieProvider : IRemoteMetadataProvider<Movie, MovieInfo>, IHasOrder
{
@@ -129,7 +131,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
public string Name => TmdbUtils.ProviderName;
/// <summary>
- /// The _TMDB settings task
+ /// The _TMDB settings task.
/// </summary>
private TmdbSettingsResult _tmdbSettings;
@@ -146,10 +148,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
using (HttpResponseInfo response = await GetMovieDbResponse(new HttpRequestOptions
{
- Url = string.Format(TmdbConfigUrl, TmdbUtils.ApiKey),
+ Url = string.Format(CultureInfo.InvariantCulture, TmdbConfigUrl, TmdbUtils.ApiKey),
CancellationToken = cancellationToken,
AcceptHeader = TmdbUtils.AcceptHeader
-
}).ConfigureAwait(false))
{
using (Stream json = response.Content)
@@ -195,7 +196,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
{
var mainResult = await FetchMainResult(id, true, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
- if (mainResult == null) return;
+ if (mainResult == null)
+ {
+ return;
+ }
var dataFilePath = GetDataFilePath(id, preferredMetadataLanguage);
@@ -241,7 +245,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
preferredLanguage = "alllang";
}
- var filename = string.Format("all-{0}.json", preferredLanguage);
+ var filename = string.Format(CultureInfo.InvariantCulture, "all-{0}.json", preferredLanguage);
return Path.Combine(path, filename);
}
@@ -272,7 +276,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
languages.Add("en");
}
- return string.Join(",", languages.ToArray());
+ return string.Join(",", languages);
}
public static string NormalizeLanguage(string language)
@@ -313,15 +317,15 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
/// <param name="id">The id.</param>
/// <param name="isTmdbId">if set to <c>true</c> [is TMDB identifier].</param>
/// <param name="language">The language.</param>
- /// <param name="cancellationToken">The cancellation token</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{CompleteMovieData}.</returns>
internal async Task<MovieResult> FetchMainResult(string id, bool isTmdbId, string language, CancellationToken cancellationToken)
{
- var url = string.Format(GetMovieInfo3, id, TmdbUtils.ApiKey);
+ var url = string.Format(CultureInfo.InvariantCulture, GetMovieInfo3, id, TmdbUtils.ApiKey);
if (!string.IsNullOrEmpty(language))
{
- url += string.Format("&language={0}", NormalizeLanguage(language));
+ url += string.Format(CultureInfo.InvariantCulture, "&language={0}", NormalizeLanguage(language));
// Get images in english and with no language
url += "&include_image_language=" + GetImageLanguagesParam(language);
@@ -344,7 +348,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
AcceptHeader = TmdbUtils.AcceptHeader,
CacheMode = cacheMode,
CacheLength = cacheLength
-
}).ConfigureAwait(false))
{
using (var json = response.Content)
@@ -374,7 +377,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
{
_logger.LogInformation("MovieDbProvider couldn't find meta for language " + language + ". Trying English...");
- url = string.Format(GetMovieInfo3, id, TmdbUtils.ApiKey) + "&language=en";
+ url = string.Format(CultureInfo.InvariantCulture, GetMovieInfo3, id, TmdbUtils.ApiKey) + "&language=en";
if (!string.IsNullOrEmpty(language))
{
@@ -389,7 +392,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
AcceptHeader = TmdbUtils.AcceptHeader,
CacheMode = cacheMode,
CacheLength = cacheLength
-
}).ConfigureAwait(false))
{
using (var json = response.Content)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSearch.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSearch.cs
index e1e34afb9..10935c655 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSearch.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSearch.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -94,7 +96,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
if (results.Count == 0)
{
- //try in english if wasn't before
+ // try in english if wasn't before
if (!string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
{
results = await GetSearchResults(name, searchType, year, "en", tmdbImageUrl, cancellationToken).ConfigureAwait(false);
@@ -128,7 +130,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
if (results.Count == 0 && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
{
- //one more time, in english
+ // one more time, in english
results = await GetSearchResults(name2, searchType, year, "en", tmdbImageUrl, cancellationToken).ConfigureAwait(false);
}
}
@@ -203,7 +205,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
remoteResult.SetProviderId(MetadataProvider.Tmdb, i.Id.ToString(_usCulture));
return remoteResult;
-
})
.ToList();
}
@@ -224,7 +225,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
Url = url3,
CancellationToken = cancellationToken,
AcceptHeader = TmdbUtils.AcceptHeader
-
}).ConfigureAwait(false))
{
using (var json = response.Content)
@@ -256,7 +256,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
remoteResult.SetProviderId(MetadataProvider.Tmdb, i.Id.ToString(_usCulture));
return remoteResult;
-
})
.ToList();
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSettings.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSettings.cs
index 03669ca67..128258ab3 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSettings.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSettings.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
@@ -5,8 +7,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
internal class TmdbImageSettings
{
public List<string> backdrop_sizes { get; set; }
+
public string secure_base_url { get; set; }
+
public List<string> poster_sizes { get; set; }
+
public List<string> profile_sizes { get; set; }
public string GetImageUrl(string image)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Music/TmdbMusicVideoProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Music/TmdbMusicVideoProvider.cs
index d173bcc9a..d4264dd4e 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Music/TmdbMusicVideoProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Music/TmdbMusicVideoProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Threading;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs
index 70cd1cd95..de74a7a4c 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs
@@ -1,18 +1,25 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.People
{
+ /// <summary>
+ /// External ID for a TMDB person.
+ /// </summary>
public class TmdbPersonExternalId : IExternalId
{
/// <inheritdoc />
- public string Name => TmdbUtils.ProviderName;
+ public string ProviderName => TmdbUtils.ProviderName;
/// <inheritdoc />
public string Key => MetadataProvider.Tmdb.ToString();
/// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Person;
+
+ /// <inheritdoc />
public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "person/{0}";
/// <inheritdoc />
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs
index 525c0072b..2faa9f835 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
@@ -98,6 +100,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
{
return 3;
}
+
if (!isLanguageEn)
{
if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
@@ -105,10 +108,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
return 2;
}
}
+
if (string.IsNullOrEmpty(i.Language))
{
return isLanguageEn ? 3 : 2;
}
+
return 0;
})
.ThenByDescending(i => i.CommunityRating ?? 0)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
index 3f28483f7..76d3f8224 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -167,12 +169,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
// TODO: This should go in PersonMetadataService, not each person provider
item.Name = id.Name;
- //item.HomePageUrl = info.homepage;
+ // item.HomePageUrl = info.homepage;
if (!string.IsNullOrWhiteSpace(info.Place_Of_Birth))
{
item.ProductionLocations = new string[] { info.Place_Of_Birth };
}
+
item.Overview = info.Biography;
if (DateTime.TryParseExact(info.Birthday, "yyyy-MM-dd", new CultureInfo("en-US"), DateTimeStyles.None, out var date))
@@ -232,7 +235,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
Url = url,
CancellationToken = cancellationToken,
AcceptHeader = TmdbUtils.AcceptHeader
-
}).ConfigureAwait(false))
{
using (var json = response.Content)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
index 3fa47d54b..77e4b2c56 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
index 01b295f86..0c55b91e0 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -141,8 +143,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
var credits = response.Credits;
if (credits != null)
{
- //Actors, Directors, Writers - all in People
- //actors come from cast
+ // Actors, Directors, Writers - all in People
+ // actors come from cast
if (credits.Cast != null)
{
foreach (var actor in credits.Cast.OrderBy(a => a.Order))
@@ -160,7 +162,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
}
}
- //and the rest from crew
+ // and the rest from crew
if (credits.Crew != null)
{
var keepTypes = new[]
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs
index f82f5f2ab..846e6095b 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Globalization;
using System.IO;
@@ -127,7 +129,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
Url = url,
CancellationToken = cancellationToken,
AcceptHeader = TmdbUtils.AcceptHeader
-
}).ConfigureAwait(false))
{
using (var json = response.Content)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
index b5456b45c..56b6e4483 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.IO;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
index c7cd672b4..11f21333c 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -63,7 +65,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
result.Item = new Season();
// Don't use moviedb season names for now until if/when we have field-level configuration
- //result.Item.Name = seasonInfo.name;
+ // result.Item.Name = seasonInfo.name;
result.Item.Name = info.Name;
@@ -79,17 +81,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
var credits = seasonInfo.Credits;
if (credits != null)
{
- //Actors, Directors, Writers - all in People
- //actors come from cast
+ // Actors, Directors, Writers - all in People
+ // actors come from cast
if (credits.Cast != null)
{
- //foreach (var actor in credits.cast.OrderBy(a => a.order)) result.Item.AddPerson(new PersonInfo { Name = actor.name.Trim(), Role = actor.character, Type = PersonType.Actor, SortOrder = actor.order });
+ // foreach (var actor in credits.cast.OrderBy(a => a.order)) result.Item.AddPerson(new PersonInfo { Name = actor.name.Trim(), Role = actor.character, Type = PersonType.Actor, SortOrder = actor.order });
}
- //and the rest from crew
+ // and the rest from crew
if (credits.Crew != null)
{
- //foreach (var person in credits.crew) result.Item.AddPerson(new PersonInfo { Name = person.name.Trim(), Role = person.job, Type = person.department });
+ // foreach (var person in credits.crew) result.Item.AddPerson(new PersonInfo { Name = person.name.Trim(), Role = person.job, Type = person.department });
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs
index 705f8041b..6ecc055d7 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs
@@ -1,18 +1,25 @@
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.TV
{
+ /// <summary>
+ /// External ID for a TMDB series.
+ /// </summary>
public class TmdbSeriesExternalId : IExternalId
{
/// <inheritdoc />
- public string Name => TmdbUtils.ProviderName;
+ public string ProviderName => TmdbUtils.ProviderName;
/// <inheritdoc />
public string Key => MetadataProvider.Tmdb.ToString();
/// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Series;
+
+ /// <inheritdoc />
public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "tv/{0}";
/// <inheritdoc />
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
index 40824d88d..95c451493 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
index 7e46a65bb..f142fd29c 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs
index ee5128db4..7e2b06257 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
diff --git a/MediaBrowser.Providers/Studios/StudioMetadataService.cs b/MediaBrowser.Providers/Studios/StudioMetadataService.cs
index 68f02433b..78042b40d 100644
--- a/MediaBrowser.Providers/Studios/StudioMetadataService.cs
+++ b/MediaBrowser.Providers/Studios/StudioMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
index cbef27a09..25f8beb40 100644
--- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
+++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.IO;
@@ -201,6 +203,5 @@ namespace MediaBrowser.Providers.Studios
}
}
}
-
}
}
diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
index c6ffc460c..3510b90cf 100644
--- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
+++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -104,6 +106,7 @@ namespace MediaBrowser.Providers.Subtitles
_logger.LogError(ex, "Error downloading subtitles from {Provider}", provider.Name);
}
}
+
return Array.Empty<RemoteSubtitleInfo>();
}
@@ -311,7 +314,6 @@ namespace MediaBrowser.Providers.Subtitles
Index = index,
ItemId = item.Id,
Type = MediaStreamType.Subtitle
-
}).First();
var path = stream.Path;
@@ -365,9 +367,7 @@ namespace MediaBrowser.Providers.Subtitles
{
Name = i.Name,
Id = GetProviderId(i.Name)
-
}).ToArray();
}
-
}
}
diff --git a/MediaBrowser.Providers/TV/DummySeasonProvider.cs b/MediaBrowser.Providers/TV/DummySeasonProvider.cs
index 6a1e6df8f..df09d13dd 100644
--- a/MediaBrowser.Providers/TV/DummySeasonProvider.cs
+++ b/MediaBrowser.Providers/TV/DummySeasonProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -40,11 +42,11 @@ namespace MediaBrowser.Providers.TV
if (hasNewSeasons)
{
- //var directoryService = new DirectoryService(_fileSystem);
+ // var directoryService = new DirectoryService(_fileSystem);
- //await series.RefreshMetadata(new MetadataRefreshOptions(directoryService), cancellationToken).ConfigureAwait(false);
+ // await series.RefreshMetadata(new MetadataRefreshOptions(directoryService), cancellationToken).ConfigureAwait(false);
- //await series.ValidateChildren(new SimpleProgress<double>(), cancellationToken, new MetadataRefreshOptions(directoryService))
+ // await series.ValidateChildren(new SimpleProgress<double>(), cancellationToken, new MetadataRefreshOptions(directoryService))
// .ConfigureAwait(false);
}
@@ -72,6 +74,7 @@ namespace MediaBrowser.Providers.TV
{
seasons = series.Children.OfType<Season>().ToList();
}
+
var existingSeason = seasons
.FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == seasonNumber);
diff --git a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs
index 50a015c7a..170f1bdd8 100644
--- a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs
+++ b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV;
diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
index aabad3ada..09850beb0 100644
--- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -108,7 +110,7 @@ namespace MediaBrowser.Providers.TV
/// <summary>
/// Returns true if a series has any seasons or episodes without season or episode numbers
- /// If this data is missing no virtual items will be added in order to prevent possible duplicates
+ /// If this data is missing no virtual items will be added in order to prevent possible duplicates.
/// </summary>
private bool HasInvalidContent(IList<BaseItem> allItems)
{
@@ -171,7 +173,7 @@ namespace MediaBrowser.Providers.TV
}
/// <summary>
- /// Removes the virtual entry after a corresponding physical version has been added
+ /// Removes the virtual entry after a corresponding physical version has been added.
/// </summary>
private bool RemoveObsoleteOrMissingEpisodes(
IEnumerable<BaseItem> allRecursiveChildren,
diff --git a/MediaBrowser.Providers/TV/SeasonMetadataService.cs b/MediaBrowser.Providers/TV/SeasonMetadataService.cs
index 1eb6af2ba..5431de623 100644
--- a/MediaBrowser.Providers/TV/SeasonMetadataService.cs
+++ b/MediaBrowser.Providers/TV/SeasonMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
index 7a753e832..4181d37ef 100644
--- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs
+++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Threading;
using System.Threading.Tasks;
@@ -67,10 +69,12 @@ namespace MediaBrowser.Providers.TV
{
return false;
}
+
if (!item.ProductionYear.HasValue)
{
return false;
}
+
return base.IsFullLocalMetadata(item);
}
diff --git a/MediaBrowser.Providers/TV/TvExternalIds.cs b/MediaBrowser.Providers/TV/TvExternalIds.cs
index bd59606e7..a6040edd1 100644
--- a/MediaBrowser.Providers/TV/TvExternalIds.cs
+++ b/MediaBrowser.Providers/TV/TvExternalIds.cs
@@ -1,6 +1,9 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
using MediaBrowser.Providers.Plugins.TheTvdb;
namespace MediaBrowser.Providers.TV
@@ -8,12 +11,15 @@ namespace MediaBrowser.Providers.TV
public class Zap2ItExternalId : IExternalId
{
/// <inheritdoc />
- public string Name => "Zap2It";
+ public string ProviderName => "Zap2It";
/// <inheritdoc />
public string Key => MetadataProvider.Zap2It.ToString();
/// <inheritdoc />
+ public ExternalIdMediaType? Type => null;
+
+ /// <inheritdoc />
public string UrlFormatString => "http://tvlistings.zap2it.com/overview.html?programSeriesId={0}";
/// <inheritdoc />
@@ -23,28 +29,33 @@ namespace MediaBrowser.Providers.TV
public class TvdbExternalId : IExternalId
{
/// <inheritdoc />
- public string Name => "TheTVDB";
+ public string ProviderName => "TheTVDB";
/// <inheritdoc />
public string Key => MetadataProvider.Tvdb.ToString();
/// <inheritdoc />
+ public ExternalIdMediaType? Type => null;
+
+ /// <inheritdoc />
public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=series&id={0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is Series;
-
}
public class TvdbSeasonExternalId : IExternalId
{
/// <inheritdoc />
- public string Name => "TheTVDB";
+ public string ProviderName => "TheTVDB";
/// <inheritdoc />
public string Key => MetadataProvider.Tvdb.ToString();
/// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Season;
+
+ /// <inheritdoc />
public string UrlFormatString => null;
/// <inheritdoc />
@@ -54,12 +65,15 @@ namespace MediaBrowser.Providers.TV
public class TvdbEpisodeExternalId : IExternalId
{
/// <inheritdoc />
- public string Name => "TheTVDB";
+ public string ProviderName => "TheTVDB";
/// <inheritdoc />
public string Key => MetadataProvider.Tvdb.ToString();
/// <inheritdoc />
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Episode;
+
+ /// <inheritdoc />
public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=episode&id={0}";
/// <inheritdoc />
diff --git a/MediaBrowser.Providers/Videos/VideoMetadataService.cs b/MediaBrowser.Providers/Videos/VideoMetadataService.cs
index 31d698f79..31c7eaac4 100644
--- a/MediaBrowser.Providers/Videos/VideoMetadataService.cs
+++ b/MediaBrowser.Providers/Videos/VideoMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
diff --git a/MediaBrowser.Providers/Years/YearMetadataService.cs b/MediaBrowser.Providers/Years/YearMetadataService.cs
index 0a8d41946..6151d12e9 100644
--- a/MediaBrowser.Providers/Years/YearMetadataService.cs
+++ b/MediaBrowser.Providers/Years/YearMetadataService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
diff --git a/README.md b/README.md
index 99a66e306..83090100d 100644
--- a/README.md
+++ b/README.md
@@ -16,8 +16,8 @@
<a href="https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/?utm_source=widget">
<img alt="Translation Status" src="https://translate.jellyfin.org/widgets/jellyfin/-/jellyfin-core/svg-badge.svg"/>
</a>
-<a href="https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=1">
-<img alt="Azure Builds" src="https://dev.azure.com/jellyfin-project/jellyfin/_apis/build/status/Jellyfin%20CI"/>
+<a href="https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=29">
+<img alt="Azure Builds" src="https://dev.azure.com/jellyfin-project/jellyfin/_apis/build/status/Jellyfin%20Server"/>
</a>
<a href="https://hub.docker.com/r/jellyfin/jellyfin">
<img alt="Docker Pull Count" src="https://img.shields.io/docker/pulls/jellyfin/jellyfin.svg"/>
diff --git a/RSSDP/DeviceAvailableEventArgs.cs b/RSSDP/DeviceAvailableEventArgs.cs
index 21ac7c631..b7d22a7df 100644
--- a/RSSDP/DeviceAvailableEventArgs.cs
+++ b/RSSDP/DeviceAvailableEventArgs.cs
@@ -10,14 +10,9 @@ namespace Rssdp
{
public IPAddress LocalIpAddress { get; set; }
- #region Fields
-
private readonly DiscoveredSsdpDevice _DiscoveredDevice;
- private readonly bool _IsNewlyDiscovered;
- #endregion
-
- #region Constructors
+ private readonly bool _IsNewlyDiscovered;
/// <summary>
/// Full constructor.
@@ -27,16 +22,15 @@ namespace Rssdp
/// <exception cref="ArgumentNullException">Thrown if the <paramref name="discoveredDevice"/> parameter is null.</exception>
public DeviceAvailableEventArgs(DiscoveredSsdpDevice discoveredDevice, bool isNewlyDiscovered)
{
- if (discoveredDevice == null) throw new ArgumentNullException(nameof(discoveredDevice));
+ if (discoveredDevice == null)
+ {
+ throw new ArgumentNullException(nameof(discoveredDevice));
+ }
_DiscoveredDevice = discoveredDevice;
_IsNewlyDiscovered = isNewlyDiscovered;
}
- #endregion
-
- #region Public Properties
-
/// <summary>
/// Returns true if the device was discovered due to an alive notification, or a search and was not already in the cache. Returns false if the item came from the cache but matched the current search request.
/// </summary>
@@ -52,8 +46,5 @@ namespace Rssdp
{
get { return _DiscoveredDevice; }
}
-
- #endregion
-
}
}
diff --git a/RSSDP/DeviceEventArgs.cs b/RSSDP/DeviceEventArgs.cs
index 05eb4a256..2455ccbfa 100644
--- a/RSSDP/DeviceEventArgs.cs
+++ b/RSSDP/DeviceEventArgs.cs
@@ -7,15 +7,8 @@ namespace Rssdp
/// </summary>
public sealed class DeviceEventArgs : EventArgs
{
-
- #region Fields
-
private readonly SsdpDevice _Device;
- #endregion
-
- #region Constructors
-
/// <summary>
/// Constructs a new instance for the specified <see cref="SsdpDevice"/>.
/// </summary>
@@ -23,15 +16,14 @@ namespace Rssdp
/// <exception cref="ArgumentNullException">Thrown if the <paramref name="device"/> argument is null.</exception>
public DeviceEventArgs(SsdpDevice device)
{
- if (device == null) throw new ArgumentNullException(nameof(device));
+ if (device == null)
+ {
+ throw new ArgumentNullException(nameof(device));
+ }
_Device = device;
}
- #endregion
-
- #region Public Properties
-
/// <summary>
/// Returns the <see cref="SsdpDevice"/> instance the event being raised for.
/// </summary>
@@ -39,8 +31,5 @@ namespace Rssdp
{
get { return _Device; }
}
-
- #endregion
-
}
}
diff --git a/RSSDP/DeviceUnavailableEventArgs.cs b/RSSDP/DeviceUnavailableEventArgs.cs
index ef04904bd..94248f39f 100644
--- a/RSSDP/DeviceUnavailableEventArgs.cs
+++ b/RSSDP/DeviceUnavailableEventArgs.cs
@@ -7,15 +7,9 @@ namespace Rssdp
/// </summary>
public sealed class DeviceUnavailableEventArgs : EventArgs
{
-
- #region Fields
-
private readonly DiscoveredSsdpDevice _DiscoveredDevice;
- private readonly bool _Expired;
-
- #endregion
- #region Constructors
+ private readonly bool _Expired;
/// <summary>
/// Full constructor.
@@ -25,16 +19,15 @@ namespace Rssdp
/// <exception cref="ArgumentNullException">Thrown if the <paramref name="discoveredDevice"/> parameter is null.</exception>
public DeviceUnavailableEventArgs(DiscoveredSsdpDevice discoveredDevice, bool expired)
{
- if (discoveredDevice == null) throw new ArgumentNullException(nameof(discoveredDevice));
+ if (discoveredDevice == null)
+ {
+ throw new ArgumentNullException(nameof(discoveredDevice));
+ }
_DiscoveredDevice = discoveredDevice;
_Expired = expired;
}
- #endregion
-
- #region Public Properties
-
/// <summary>
/// Returns true if the device is considered unavailable because it's cached information expired before a new alive notification or search result was received. Returns false if the device is unavailable because it sent an explicit notification of it's unavailability.
/// </summary>
@@ -50,7 +43,5 @@ namespace Rssdp
{
get { return _DiscoveredDevice; }
}
-
- #endregion
}
}
diff --git a/RSSDP/DiscoveredSsdpDevice.cs b/RSSDP/DiscoveredSsdpDevice.cs
index 1244ce523..322bd55e5 100644
--- a/RSSDP/DiscoveredSsdpDevice.cs
+++ b/RSSDP/DiscoveredSsdpDevice.cs
@@ -10,15 +10,8 @@ namespace Rssdp
/// <seealso cref="Infrastructure.ISsdpDeviceLocator"/>
public sealed class DiscoveredSsdpDevice
{
-
- #region Fields
-
private DateTimeOffset _AsAt;
- #endregion
-
- #region Public Properties
-
/// <summary>
/// Sets or returns the type of notification, being either a uuid, device type, service type or upnp:rootdevice.
/// </summary>
@@ -45,6 +38,7 @@ namespace Rssdp
public DateTimeOffset AsAt
{
get { return _AsAt; }
+
set
{
if (_AsAt != value)
@@ -55,14 +49,10 @@ namespace Rssdp
}
/// <summary>
- /// Returns the headers from the SSDP device response message
+ /// Returns the headers from the SSDP device response message.
/// </summary>
public HttpHeaders ResponseHeaders { get; set; }
- #endregion
-
- #region Public Methods
-
/// <summary>
/// Returns true if this device information has expired, based on the current date/time, and the <see cref="CacheLifetime"/> &amp; <see cref="AsAt"/> properties.
/// </summary>
@@ -72,10 +62,6 @@ namespace Rssdp
return this.CacheLifetime == TimeSpan.Zero || this.AsAt.Add(this.CacheLifetime) <= DateTimeOffset.Now;
}
- #endregion
-
- #region Overrides
-
/// <summary>
/// Returns the device's <see cref="Usn"/> value.
/// </summary>
@@ -84,7 +70,5 @@ namespace Rssdp
{
return this.Usn;
}
-
- #endregion
}
}
diff --git a/RSSDP/DisposableManagedObjectBase.cs b/RSSDP/DisposableManagedObjectBase.cs
index 39589f022..66a0c5ec4 100644
--- a/RSSDP/DisposableManagedObjectBase.cs
+++ b/RSSDP/DisposableManagedObjectBase.cs
@@ -9,9 +9,6 @@ namespace Rssdp.Infrastructure
/// </summary>
public abstract class DisposableManagedObjectBase : IDisposable
{
-
- #region Public Methods
-
/// <summary>
/// Override this method and dispose any objects you own the lifetime of if disposing is true;
/// </summary>
@@ -26,13 +23,12 @@ namespace Rssdp.Infrastructure
/// <seealso cref="Dispose()"/>
protected virtual void ThrowIfDisposed()
{
- if (this.IsDisposed) throw new ObjectDisposedException(this.GetType().FullName);
+ if (this.IsDisposed)
+ {
+ throw new ObjectDisposedException(this.GetType().FullName);
+ }
}
- #endregion
-
- #region Public Properties
-
/// <summary>
/// Sets or returns a boolean indicating whether or not this instance has been disposed.
/// </summary>
@@ -43,8 +39,6 @@ namespace Rssdp.Infrastructure
private set;
}
- #endregion
-
public string BuildMessage(string header, Dictionary<string, string> values)
{
var builder = new StringBuilder();
@@ -63,8 +57,6 @@ namespace Rssdp.Infrastructure
return builder.ToString();
}
- #region IDisposable Members
-
/// <summary>
/// Disposes this object instance and all internally managed resources.
/// </summary>
@@ -79,7 +71,5 @@ namespace Rssdp.Infrastructure
Dispose(true);
}
-
- #endregion
}
}
diff --git a/RSSDP/HttpParserBase.cs b/RSSDP/HttpParserBase.cs
index 773a06cdb..a40612bc2 100644
--- a/RSSDP/HttpParserBase.cs
+++ b/RSSDP/HttpParserBase.cs
@@ -11,16 +11,9 @@ namespace Rssdp.Infrastructure
/// <typeparam name="T"></typeparam>
public abstract class HttpParserBase<T> where T : new()
{
-
- #region Fields
-
private readonly string[] LineTerminators = new string[] { "\r\n", "\n" };
private readonly char[] SeparatorCharacters = new char[] { ',', ';' };
- #endregion
-
- #region Public Methods
-
/// <summary>
/// Parses the <paramref name="data"/> provided into either a <see cref="HttpRequestMessage"/> or <see cref="HttpResponseMessage"/> object.
/// </summary>
@@ -38,15 +31,26 @@ namespace Rssdp.Infrastructure
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Honestly, it's fine. MemoryStream doesn't mind.")]
protected virtual void Parse(T message, System.Net.Http.Headers.HttpHeaders headers, string data)
{
- if (data == null) throw new ArgumentNullException(nameof(data));
- if (data.Length == 0) throw new ArgumentException("data cannot be an empty string.", nameof(data));
- if (!LineTerminators.Any(data.Contains)) throw new ArgumentException("data is not a valid request, it does not contain any CRLF/LF terminators.", nameof(data));
+ if (data == null)
+ {
+ throw new ArgumentNullException(nameof(data));
+ }
+
+ if (data.Length == 0)
+ {
+ throw new ArgumentException("data cannot be an empty string.", nameof(data));
+ }
+
+ if (!LineTerminators.Any(data.Contains))
+ {
+ throw new ArgumentException("data is not a valid request, it does not contain any CRLF/LF terminators.", nameof(data));
+ }
using (var retVal = new ByteArrayContent(Array.Empty<byte>()))
{
var lines = data.Split(LineTerminators, StringSplitOptions.None);
- //First line is the 'request' line containing http protocol details like method, uri, http version etc.
+ // First line is the 'request' line containing http protocol details like method, uri, http version etc.
ParseStatusLine(lines[0], message);
ParseHeaders(headers, retVal.Headers, lines);
@@ -73,18 +77,20 @@ namespace Rssdp.Infrastructure
/// <returns>A <see cref="Version"/> object containing the parsed version data.</returns>
protected Version ParseHttpVersion(string versionData)
{
- if (versionData == null) throw new ArgumentNullException(nameof(versionData));
+ if (versionData == null)
+ {
+ throw new ArgumentNullException(nameof(versionData));
+ }
var versionSeparatorIndex = versionData.IndexOf('/');
- if (versionSeparatorIndex <= 0 || versionSeparatorIndex == versionData.Length) throw new ArgumentException("request header line is invalid. Http Version not supplied or incorrect format.", nameof(versionData));
+ if (versionSeparatorIndex <= 0 || versionSeparatorIndex == versionData.Length)
+ {
+ throw new ArgumentException("request header line is invalid. Http Version not supplied or incorrect format.", nameof(versionData));
+ }
return Version.Parse(versionData.Substring(versionSeparatorIndex + 1));
}
- #endregion
-
- #region Private Methods
-
/// <summary>
/// Parses a line from an HTTP request or response message containing a header name and value pair.
/// </summary>
@@ -93,35 +99,39 @@ namespace Rssdp.Infrastructure
/// <param name="contentHeaders">A reference to a <see cref="System.Net.Http.Headers.HttpHeaders"/> collection for the message content, to which the parsed header will be added.</param>
private void ParseHeader(string line, System.Net.Http.Headers.HttpHeaders headers, System.Net.Http.Headers.HttpHeaders contentHeaders)
{
- //Header format is
- //name: value
+ // Header format is
+ // name: value
var headerKeySeparatorIndex = line.IndexOf(":", StringComparison.OrdinalIgnoreCase);
var headerName = line.Substring(0, headerKeySeparatorIndex).Trim();
var headerValue = line.Substring(headerKeySeparatorIndex + 1).Trim();
- //Not sure how to determine where request headers and and content headers begin,
- //at least not without a known set of headers (general headers first the content headers)
- //which seems like a bad way of doing it. So we'll assume if it's a known content header put it there
- //else use request headers.
+ // Not sure how to determine where request headers and and content headers begin,
+ // at least not without a known set of headers (general headers first the content headers)
+ // which seems like a bad way of doing it. So we'll assume if it's a known content header put it there
+ // else use request headers.
var values = ParseValues(headerValue);
var headersToAddTo = IsContentHeader(headerName) ? contentHeaders : headers;
if (values.Count > 1)
+ {
headersToAddTo.TryAddWithoutValidation(headerName, values);
+ }
else
+ {
headersToAddTo.TryAddWithoutValidation(headerName, values.First());
+ }
}
private int ParseHeaders(System.Net.Http.Headers.HttpHeaders headers, System.Net.Http.Headers.HttpHeaders contentHeaders, string[] lines)
{
- //Blank line separates headers from content, so read headers until we find blank line.
+ // Blank line separates headers from content, so read headers until we find blank line.
int lineIndex = 1;
string line = null, nextLine = null;
while (lineIndex + 1 < lines.Length && !String.IsNullOrEmpty((line = lines[lineIndex++])))
{
- //If the following line starts with space or tab (or any whitespace), it is really part of this header but split for human readability.
- //Combine these lines into a single comma separated style header for easier parsing.
+ // If the following line starts with space or tab (or any whitespace), it is really part of this header but split for human readability.
+ // Combine these lines into a single comma separated style header for easier parsing.
while (lineIndex < lines.Length && !String.IsNullOrEmpty((nextLine = lines[lineIndex])))
{
if (nextLine.Length > 0 && Char.IsWhiteSpace(nextLine[0]))
@@ -130,11 +140,14 @@ namespace Rssdp.Infrastructure
lineIndex++;
}
else
+ {
break;
+ }
}
ParseHeader(line, headers, contentHeaders);
}
+
return lineIndex;
}
@@ -153,7 +166,9 @@ namespace Rssdp.Infrastructure
var indexOfSeparator = headerValue.IndexOfAny(SeparatorCharacters);
if (indexOfSeparator <= 0)
+ {
values.Add(headerValue);
+ }
else
{
var segments = headerValue.Split(SeparatorCharacters);
@@ -163,13 +178,17 @@ namespace Rssdp.Infrastructure
{
var segment = segments[segmentIndex];
if (segment.Trim().StartsWith("\"", StringComparison.OrdinalIgnoreCase))
+ {
segment = CombineQuotedSegments(segments, ref segmentIndex, segment);
+ }
values.Add(segment);
}
}
else
+ {
values.AddRange(segments);
+ }
}
return values;
@@ -192,17 +211,20 @@ namespace Rssdp.Infrastructure
}
if (index + 1 < segments.Length)
+ {
trimmedSegment += "," + segments[index + 1].TrimEnd();
+ }
}
segmentIndex = segments.Length;
if (trimmedSegment.StartsWith("\"", StringComparison.OrdinalIgnoreCase) && trimmedSegment.EndsWith("\"", StringComparison.OrdinalIgnoreCase))
+ {
return trimmedSegment.Substring(1, trimmedSegment.Length - 2);
+ }
else
+ {
return trimmedSegment;
+ }
}
-
- #endregion
-
}
}
diff --git a/RSSDP/HttpRequestParser.cs b/RSSDP/HttpRequestParser.cs
index 279ef883c..4114195a6 100644
--- a/RSSDP/HttpRequestParser.cs
+++ b/RSSDP/HttpRequestParser.cs
@@ -9,17 +9,10 @@ namespace Rssdp.Infrastructure
/// </summary>
public sealed class HttpRequestParser : HttpParserBase<HttpRequestMessage>
{
-
- #region Fields & Constants
-
private readonly string[] ContentHeaderNames = new string[]
- {
- "Allow", "Content-Disposition", "Content-Encoding", "Content-Language", "Content-Length", "Content-Location", "Content-MD5", "Content-Range", "Content-Type", "Expires", "Last-Modified"
- };
-
- #endregion
-
- #region Public Methods
+ {
+ "Allow", "Content-Disposition", "Content-Encoding", "Content-Language", "Content-Length", "Content-Location", "Content-MD5", "Content-Range", "Content-Type", "Expires", "Last-Modified"
+ };
/// <summary>
/// Parses the specified data into a <see cref="HttpRequestMessage"/> instance.
@@ -41,14 +34,12 @@ namespace Rssdp.Infrastructure
finally
{
if (retVal != null)
+ {
retVal.Dispose();
+ }
}
}
- #endregion
-
- #region Overrides
-
/// <summary>
/// Used to parse the first line of an HTTP request or response and assign the values to the appropriate properties on the <paramref name="message"/>.
/// </summary>
@@ -56,18 +47,32 @@ namespace Rssdp.Infrastructure
/// <param name="message">Either a <see cref="HttpResponseMessage"/> or <see cref="HttpRequestMessage"/> to assign the parsed values to.</param>
protected override void ParseStatusLine(string data, HttpRequestMessage message)
{
- if (data == null) throw new ArgumentNullException(nameof(data));
- if (message == null) throw new ArgumentNullException(nameof(message));
+ if (data == null)
+ {
+ throw new ArgumentNullException(nameof(data));
+ }
+
+ if (message == null)
+ {
+ throw new ArgumentNullException(nameof(message));
+ }
var parts = data.Split(' ');
- if (parts.Length < 2) throw new ArgumentException("Status line is invalid. Insufficient status parts.", nameof(data));
+ if (parts.Length < 2)
+ {
+ throw new ArgumentException("Status line is invalid. Insufficient status parts.", nameof(data));
+ }
message.Method = new HttpMethod(parts[0].Trim());
Uri requestUri;
if (Uri.TryCreate(parts[1].Trim(), UriKind.RelativeOrAbsolute, out requestUri))
+ {
message.RequestUri = requestUri;
+ }
else
+ {
System.Diagnostics.Debug.WriteLine(parts[1]);
+ }
if (parts.Length >= 3)
{
@@ -83,8 +88,5 @@ namespace Rssdp.Infrastructure
{
return ContentHeaderNames.Contains(headerName, StringComparer.OrdinalIgnoreCase);
}
-
- #endregion
-
}
}
diff --git a/RSSDP/HttpResponseParser.cs b/RSSDP/HttpResponseParser.cs
index b96eaf625..0dd4bb45a 100644
--- a/RSSDP/HttpResponseParser.cs
+++ b/RSSDP/HttpResponseParser.cs
@@ -10,17 +10,10 @@ namespace Rssdp.Infrastructure
/// </summary>
public sealed class HttpResponseParser : HttpParserBase<HttpResponseMessage>
{
-
- #region Fields & Constants
-
private readonly string[] ContentHeaderNames = new string[]
- {
- "Allow", "Content-Disposition", "Content-Encoding", "Content-Language", "Content-Length", "Content-Location", "Content-MD5", "Content-Range", "Content-Type", "Expires", "Last-Modified"
- };
-
- #endregion
-
- #region Public Methods
+ {
+ "Allow", "Content-Disposition", "Content-Encoding", "Content-Language", "Content-Length", "Content-Location", "Content-MD5", "Content-Range", "Content-Type", "Expires", "Last-Modified"
+ };
/// <summary>
/// Parses the specified data into a <see cref="HttpResponseMessage"/> instance.
@@ -41,16 +34,14 @@ namespace Rssdp.Infrastructure
catch
{
if (retVal != null)
+ {
retVal.Dispose();
+ }
throw;
}
}
- #endregion
-
- #region Overrides Methods
-
/// <summary>
/// Returns a boolean indicating whether the specified HTTP header name represents a content header (true), or a message header (false).
/// </summary>
@@ -68,17 +59,29 @@ namespace Rssdp.Infrastructure
/// <param name="message">Either a <see cref="HttpResponseMessage"/> or <see cref="HttpRequestMessage"/> to assign the parsed values to.</param>
protected override void ParseStatusLine(string data, HttpResponseMessage message)
{
- if (data == null) throw new ArgumentNullException(nameof(data));
- if (message == null) throw new ArgumentNullException(nameof(message));
+ if (data == null)
+ {
+ throw new ArgumentNullException(nameof(data));
+ }
+
+ if (message == null)
+ {
+ throw new ArgumentNullException(nameof(message));
+ }
var parts = data.Split(' ');
- if (parts.Length < 2) throw new ArgumentException("data status line is invalid. Insufficient status parts.", nameof(data));
+ if (parts.Length < 2)
+ {
+ throw new ArgumentException("data status line is invalid. Insufficient status parts.", nameof(data));
+ }
message.Version = ParseHttpVersion(parts[0].Trim());
int statusCode = -1;
if (!Int32.TryParse(parts[1].Trim(), out statusCode))
+ {
throw new ArgumentException("data status line is invalid. Status code is not a valid integer.", nameof(data));
+ }
message.StatusCode = (HttpStatusCode)statusCode;
@@ -87,7 +90,5 @@ namespace Rssdp.Infrastructure
message.ReasonPhrase = parts[2].Trim();
}
}
-
- #endregion
}
}
diff --git a/RSSDP/IEnumerableExtensions.cs b/RSSDP/IEnumerableExtensions.cs
index 371454893..1f0daad3e 100644
--- a/RSSDP/IEnumerableExtensions.cs
+++ b/RSSDP/IEnumerableExtensions.cs
@@ -8,8 +8,15 @@ namespace Rssdp.Infrastructure
{
public static IEnumerable<T> SelectManyRecursive<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> selector)
{
- if (source == null) throw new ArgumentNullException(nameof(source));
- if (selector == null) throw new ArgumentNullException(nameof(selector));
+ if (source == null)
+ {
+ throw new ArgumentNullException(nameof(source));
+ }
+
+ if (selector == null)
+ {
+ throw new ArgumentNullException(nameof(selector));
+ }
return !source.Any() ? source :
source.Concat(
diff --git a/RSSDP/ISsdpCommunicationsServer.cs b/RSSDP/ISsdpCommunicationsServer.cs
index 8cf65df11..aa35811ef 100644
--- a/RSSDP/ISsdpCommunicationsServer.cs
+++ b/RSSDP/ISsdpCommunicationsServer.cs
@@ -10,9 +10,6 @@ namespace Rssdp.Infrastructure
/// </summary>
public interface ISsdpCommunicationsServer : IDisposable
{
-
- #region Events
-
/// <summary>
/// Raised when a HTTPU request message is received by a socket (unicast or multicast).
/// </summary>
@@ -23,10 +20,6 @@ namespace Rssdp.Infrastructure
/// </summary>
event EventHandler<ResponseReceivedEventArgs> ResponseReceived;
- #endregion
-
- #region Methods
-
/// <summary>
/// Causes the server to begin listening for multicast messages, being SSDP search requests and notifications.
/// </summary>
@@ -48,10 +41,6 @@ namespace Rssdp.Infrastructure
Task SendMulticastMessage(string message, IPAddress fromLocalIpAddress, CancellationToken cancellationToken);
Task SendMulticastMessage(string message, int sendCount, IPAddress fromLocalIpAddress, CancellationToken cancellationToken);
- #endregion
-
- #region Properties
-
/// <summary>
/// Gets or sets a boolean value indicating whether or not this instance is shared amongst multiple <see cref="SsdpDeviceLocatorBase"/> and/or <see cref="ISsdpDevicePublisher"/> instances.
/// </summary>
@@ -59,8 +48,5 @@ namespace Rssdp.Infrastructure
/// <para>If true, disposing an instance of a <see cref="SsdpDeviceLocatorBase"/>or a <see cref="ISsdpDevicePublisher"/> will not dispose this comms server instance. The calling code is responsible for managing the lifetime of the server.</para>
/// </remarks>
bool IsShared { get; set; }
-
- #endregion
-
}
}
diff --git a/RSSDP/ISsdpDeviceLocator.cs b/RSSDP/ISsdpDeviceLocator.cs
index 8f0d39e75..413055643 100644
--- a/RSSDP/ISsdpDeviceLocator.cs
+++ b/RSSDP/ISsdpDeviceLocator.cs
@@ -13,9 +13,6 @@ namespace Rssdp.Infrastructure
/// <seealso cref="ISsdpDevicePublisher"/>
public interface ISsdpDeviceLocator
{
-
- #region Events
-
/// <summary>
/// Event raised when a device becomes available or is found by a search request.
/// </summary>
@@ -34,10 +31,6 @@ namespace Rssdp.Infrastructure
/// <seealso cref="StopListeningForNotifications"/>
event EventHandler<DeviceUnavailableEventArgs> DeviceUnavailable;
- #endregion
-
- #region Properties
-
/// <summary>
/// Sets or returns a string containing the filter for notifications. Notifications not matching the filter will not raise the <see cref="DeviceAvailable"/> or <see cref="DeviceUnavailable"/> events.
/// </summary>
@@ -58,12 +51,6 @@ namespace Rssdp.Infrastructure
set;
}
- #endregion
-
- #region Methods
-
- #region SearchAsync Overloads
-
/// <summary>
/// Aynchronously performs a search for all devices using the default search timeout, and returns an awaitable task that can be used to retrieve the results.
/// </summary>
@@ -108,8 +95,6 @@ namespace Rssdp.Infrastructure
/// <returns>A task whose result is an <see cref="System.Collections.Generic.IEnumerable{T}"/> of <see cref="DiscoveredSsdpDevice" /> instances, representing all found devices.</returns>
System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<DiscoveredSsdpDevice>> SearchAsync(TimeSpan searchWaitTime);
- #endregion
-
/// <summary>
/// Starts listening for broadcast notifications of service availability.
/// </summary>
@@ -134,8 +119,5 @@ namespace Rssdp.Infrastructure
/// <seealso cref="DeviceUnavailable"/>
/// <seealso cref="NotificationFilter"/>
void StopListeningForNotifications();
-
- #endregion
-
}
}
diff --git a/RSSDP/RequestReceivedEventArgs.cs b/RSSDP/RequestReceivedEventArgs.cs
index b753950f0..025c4d2e0 100644
--- a/RSSDP/RequestReceivedEventArgs.cs
+++ b/RSSDP/RequestReceivedEventArgs.cs
@@ -9,17 +9,12 @@ namespace Rssdp.Infrastructure
/// </summary>
public sealed class RequestReceivedEventArgs : EventArgs
{
- #region Fields
-
private readonly HttpRequestMessage _Message;
- private readonly IPEndPoint _ReceivedFrom;
- #endregion
+ private readonly IPEndPoint _ReceivedFrom;
public IPAddress LocalIpAddress { get; private set; }
- #region Constructors
-
/// <summary>
/// Full constructor.
/// </summary>
@@ -30,10 +25,6 @@ namespace Rssdp.Infrastructure
LocalIpAddress = localIpAddress;
}
- #endregion
-
- #region Public Properties
-
/// <summary>
/// The <see cref="HttpRequestMessage"/> that was received.
/// </summary>
@@ -49,7 +40,5 @@ namespace Rssdp.Infrastructure
{
get { return _ReceivedFrom; }
}
-
- #endregion
}
}
diff --git a/RSSDP/ResponseReceivedEventArgs.cs b/RSSDP/ResponseReceivedEventArgs.cs
index f9f9c3040..708113da1 100644
--- a/RSSDP/ResponseReceivedEventArgs.cs
+++ b/RSSDP/ResponseReceivedEventArgs.cs
@@ -9,17 +9,11 @@ namespace Rssdp.Infrastructure
/// </summary>
public sealed class ResponseReceivedEventArgs : EventArgs
{
-
public IPAddress LocalIpAddress { get; set; }
- #region Fields
-
private readonly HttpResponseMessage _Message;
- private readonly IPEndPoint _ReceivedFrom;
-
- #endregion
- #region Constructors
+ private readonly IPEndPoint _ReceivedFrom;
/// <summary>
/// Full constructor.
@@ -30,10 +24,6 @@ namespace Rssdp.Infrastructure
_ReceivedFrom = receivedFrom;
}
- #endregion
-
- #region Public Properties
-
/// <summary>
/// The <see cref="HttpResponseMessage"/> that was received.
/// </summary>
@@ -49,7 +39,5 @@ namespace Rssdp.Infrastructure
{
get { return _ReceivedFrom; }
}
-
- #endregion
}
}
diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs
index 18097ef24..a4be32e7d 100644
--- a/RSSDP/SsdpCommunicationsServer.cs
+++ b/RSSDP/SsdpCommunicationsServer.cs
@@ -8,7 +8,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging;
@@ -19,9 +18,6 @@ namespace Rssdp.Infrastructure
/// </summary>
public sealed class SsdpCommunicationsServer : DisposableManagedObjectBase, ISsdpCommunicationsServer
{
-
- #region Fields
-
/* We could technically use one socket listening on port 1900 for everything.
* This should get both multicast (notifications) and unicast (search response) messages, however
* this often doesn't work under Windows because the MS SSDP service is running. If that service
@@ -46,8 +42,7 @@ namespace Rssdp.Infrastructure
private HttpResponseParser _ResponseParser;
private readonly ILogger _logger;
private ISocketFactory _SocketFactory;
- private readonly INetworkManager _networkManager;
- private readonly IServerConfigurationManager _config;
+ private readonly INetworkManager _networkManager;
private int _LocalPort;
private int _MulticastTtl;
@@ -55,10 +50,6 @@ namespace Rssdp.Infrastructure
private bool _IsShared;
private readonly bool _enableMultiSocketBinding;
- #endregion
-
- #region Events
-
/// <summary>
/// Raised when a HTTPU request message is received by a socket (unicast or multicast).
/// </summary>
@@ -69,19 +60,15 @@ namespace Rssdp.Infrastructure
/// </summary>
public event EventHandler<ResponseReceivedEventArgs> ResponseReceived;
- #endregion
-
- #region Constructors
-
/// <summary>
/// Minimum constructor.
/// </summary>
/// <exception cref="ArgumentNullException">The <paramref name="socketFactory"/> argument is null.</exception>
- public SsdpCommunicationsServer(IServerConfigurationManager config, ISocketFactory socketFactory,
+ public SsdpCommunicationsServer(ISocketFactory socketFactory,
INetworkManager networkManager, ILogger logger, bool enableMultiSocketBinding)
: this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive, networkManager, logger, enableMultiSocketBinding)
{
- _config = config;
+
}
/// <summary>
@@ -91,8 +78,15 @@ namespace Rssdp.Infrastructure
/// <exception cref="ArgumentOutOfRangeException">The <paramref name="multicastTimeToLive"/> argument is less than or equal to zero.</exception>
public SsdpCommunicationsServer(ISocketFactory socketFactory, int localPort, int multicastTimeToLive, INetworkManager networkManager, ILogger logger, bool enableMultiSocketBinding)
{
- if (socketFactory == null) throw new ArgumentNullException(nameof(socketFactory));
- if (multicastTimeToLive <= 0) throw new ArgumentOutOfRangeException(nameof(multicastTimeToLive), "multicastTimeToLive must be greater than zero.");
+ if (socketFactory == null)
+ {
+ throw new ArgumentNullException(nameof(socketFactory));
+ }
+
+ if (multicastTimeToLive <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(multicastTimeToLive), "multicastTimeToLive must be greater than zero.");
+ }
_BroadcastListenSocketSynchroniser = new object();
_SendSocketSynchroniser = new object();
@@ -109,10 +103,6 @@ namespace Rssdp.Infrastructure
_enableMultiSocketBinding = enableMultiSocketBinding;
}
- #endregion
-
- #region Public Methods
-
/// <summary>
/// Causes the server to begin listening for multicast messages, being SSDP search requests and notifications.
/// </summary>
@@ -166,7 +156,10 @@ namespace Rssdp.Infrastructure
/// </summary>
public async Task SendMessage(byte[] messageData, IPEndPoint destination, IPAddress fromLocalIpAddress, CancellationToken cancellationToken)
{
- if (messageData == null) throw new ArgumentNullException(nameof(messageData));
+ if (messageData == null)
+ {
+ throw new ArgumentNullException(nameof(messageData));
+ }
ThrowIfDisposed();
@@ -195,11 +188,9 @@ namespace Rssdp.Infrastructure
}
catch (ObjectDisposedException)
{
-
}
catch (OperationCanceledException)
{
-
}
catch (Exception ex)
{
@@ -251,7 +242,10 @@ namespace Rssdp.Infrastructure
/// </summary>
public async Task SendMulticastMessage(string message, int sendCount, IPAddress fromLocalIpAddress, CancellationToken cancellationToken)
{
- if (message == null) throw new ArgumentNullException(nameof(message));
+ if (message == null)
+ {
+ throw new ArgumentNullException(nameof(message));
+ }
byte[] messageData = Encoding.UTF8.GetBytes(message);
@@ -300,10 +294,6 @@ namespace Rssdp.Infrastructure
}
}
- #endregion
-
- #region Public Properties
-
/// <summary>
/// Gets or sets a boolean value indicating whether or not this instance is shared amongst multiple <see cref="SsdpDeviceLocatorBase"/> and/or <see cref="ISsdpDevicePublisher"/> instances.
/// </summary>
@@ -313,13 +303,10 @@ namespace Rssdp.Infrastructure
public bool IsShared
{
get { return _IsShared; }
+
set { _IsShared = value; }
}
- #endregion
-
- #region Overrides
-
/// <summary>
/// Stops listening for requests, disposes this instance and all internal resources.
/// </summary>
@@ -334,10 +321,6 @@ namespace Rssdp.Infrastructure
}
}
- #endregion
-
- #region Private Methods
-
private Task SendMessageIfSocketNotDisposed(byte[] messageData, IPEndPoint destination, IPAddress fromLocalIpAddress, CancellationToken cancellationToken)
{
var sockets = _sendSockets;
@@ -356,7 +339,6 @@ namespace Rssdp.Infrastructure
private ISocket ListenForBroadcastsAsync()
{
var socket = _SocketFactory.CreateUdpMulticastSocket(SsdpConstants.MulticastLocalAdminAddress, _MulticastTtl, SsdpConstants.MulticastPort);
-
_ = ListenToSocketInternal(socket);
return socket;
@@ -370,13 +352,13 @@ namespace Rssdp.Infrastructure
if (_enableMultiSocketBinding)
{
- foreach (var address in _networkManager.GetLocalIpAddresses(_config.Configuration.IgnoreVirtualInterfaces))
+ foreach (var address in _networkManager.GetLocalIpAddresses())
{
if (address.AddressFamily == AddressFamily.InterNetworkV6)
{
// Not support IPv6 right now
continue;
- }
+ }
try
{
@@ -485,9 +467,9 @@ namespace Rssdp.Infrastructure
private void OnRequestReceived(HttpRequestMessage data, IPEndPoint remoteEndPoint, IPAddress receivedOnLocalIpAddress)
{
- //SSDP specification says only * is currently used but other uri's might
- //be implemented in the future and should be ignored unless understood.
- //Section 4.2 - http://tools.ietf.org/html/draft-cai-ssdp-v1-03#page-11
+ // SSDP specification says only * is currently used but other uri's might
+ // be implemented in the future and should be ignored unless understood.
+ // Section 4.2 - http://tools.ietf.org/html/draft-cai-ssdp-v1-03#page-11
if (data.RequestUri.ToString() != "*")
{
return;
@@ -495,20 +477,21 @@ namespace Rssdp.Infrastructure
var handlers = this.RequestReceived;
if (handlers != null)
+ {
handlers(this, new RequestReceivedEventArgs(data, remoteEndPoint, receivedOnLocalIpAddress));
+ }
}
private void OnResponseReceived(HttpResponseMessage data, IPEndPoint endPoint, IPAddress localIpAddress)
{
var handlers = this.ResponseReceived;
if (handlers != null)
+ {
handlers(this, new ResponseReceivedEventArgs(data, endPoint)
{
LocalIpAddress = localIpAddress
});
+ }
}
-
- #endregion
-
}
}
diff --git a/RSSDP/SsdpConstants.cs b/RSSDP/SsdpConstants.cs
index 28a014fce..798f050e1 100644
--- a/RSSDP/SsdpConstants.cs
+++ b/RSSDP/SsdpConstants.cs
@@ -57,6 +57,5 @@ namespace Rssdp.Infrastructure
internal const string SsdpByeByeNotification = "ssdp:byebye";
internal const int UdpResendCount = 3;
-
}
}
diff --git a/RSSDP/SsdpDevice.cs b/RSSDP/SsdpDevice.cs
index 09f729e83..4005d836d 100644
--- a/RSSDP/SsdpDevice.cs
+++ b/RSSDP/SsdpDevice.cs
@@ -15,9 +15,6 @@ namespace Rssdp
/// <seealso cref="SsdpEmbeddedDevice"/>
public abstract class SsdpDevice
{
-
- #region Fields
-
private string _Udn;
private string _DeviceType;
private string _DeviceTypeNamespace;
@@ -25,10 +22,6 @@ namespace Rssdp
private IList<SsdpDevice> _Devices;
- #endregion
-
- #region Events
-
/// <summary>
/// Raised when a new child device is added.
/// </summary>
@@ -43,10 +36,6 @@ namespace Rssdp
/// <seealso cref="DeviceRemoved"/>
public event EventHandler<DeviceEventArgs> DeviceRemoved;
- #endregion
-
- #region Constructors
-
/// <summary>
/// Derived type constructor, allows constructing a device with no parent. Should only be used from derived types that are or inherit from <see cref="SsdpRootDevice"/>.
/// </summary>
@@ -60,23 +49,19 @@ namespace Rssdp
this.Devices = new ReadOnlyCollection<SsdpDevice>(_Devices);
}
- #endregion
-
public SsdpRootDevice ToRootDevice()
{
var device = this;
var rootDevice = device as SsdpRootDevice;
if (rootDevice == null)
+ {
rootDevice = ((SsdpEmbeddedDevice)device).RootDevice;
+ }
return rootDevice;
}
- #region Public Properties
-
- #region UPnP Device Description Properties
-
/// <summary>
/// Sets or returns the core device type (not including namespace, version etc.). Required.
/// </summary>
@@ -90,6 +75,7 @@ namespace Rssdp
{
return _DeviceType;
}
+
set
{
_DeviceType = value;
@@ -111,6 +97,7 @@ namespace Rssdp
{
return _DeviceTypeNamespace;
}
+
set
{
_DeviceTypeNamespace = value;
@@ -130,6 +117,7 @@ namespace Rssdp
{
return _DeviceVersion;
}
+
set
{
_DeviceVersion = value;
@@ -177,10 +165,15 @@ namespace Rssdp
get
{
if (String.IsNullOrEmpty(_Udn) && !String.IsNullOrEmpty(this.Uuid))
+ {
return "uuid:" + this.Uuid;
+ }
else
+ {
return _Udn;
+ }
}
+
set
{
_Udn = value;
@@ -248,8 +241,6 @@ namespace Rssdp
/// </remarks>
public Uri PresentationUrl { get; set; }
- #endregion
-
/// <summary>
/// Returns a read-only enumerable set of <see cref="SsdpDevice"/> objects representing children of this device. Child devices are optional.
/// </summary>
@@ -261,10 +252,6 @@ namespace Rssdp
private set;
}
- #endregion
-
- #region Public Methods
-
/// <summary>
/// Adds a child device to the <see cref="Devices"/> collection.
/// </summary>
@@ -278,9 +265,20 @@ namespace Rssdp
/// <seealso cref="DeviceAdded"/>
public void AddDevice(SsdpEmbeddedDevice device)
{
- if (device == null) throw new ArgumentNullException(nameof(device));
- if (device.RootDevice != null && device.RootDevice != this.ToRootDevice()) throw new InvalidOperationException("This device is already associated with a different root device (has been added as a child in another branch).");
- if (device == this) throw new InvalidOperationException("Can't add device to itself.");
+ if (device == null)
+ {
+ throw new ArgumentNullException(nameof(device));
+ }
+
+ if (device.RootDevice != null && device.RootDevice != this.ToRootDevice())
+ {
+ throw new InvalidOperationException("This device is already associated with a different root device (has been added as a child in another branch).");
+ }
+
+ if (device == this)
+ {
+ throw new InvalidOperationException("Can't add device to itself.");
+ }
bool wasAdded = false;
lock (_Devices)
@@ -291,7 +289,9 @@ namespace Rssdp
}
if (wasAdded)
+ {
OnDeviceAdded(device);
+ }
}
/// <summary>
@@ -306,7 +306,10 @@ namespace Rssdp
/// <seealso cref="DeviceRemoved"/>
public void RemoveDevice(SsdpEmbeddedDevice device)
{
- if (device == null) throw new ArgumentNullException(nameof(device));
+ if (device == null)
+ {
+ throw new ArgumentNullException(nameof(device));
+ }
bool wasRemoved = false;
lock (_Devices)
@@ -319,7 +322,9 @@ namespace Rssdp
}
if (wasRemoved)
+ {
OnDeviceRemoved(device);
+ }
}
/// <summary>
@@ -332,7 +337,9 @@ namespace Rssdp
{
var handlers = this.DeviceAdded;
if (handlers != null)
+ {
handlers(this, new DeviceEventArgs(device));
+ }
}
/// <summary>
@@ -345,10 +352,9 @@ namespace Rssdp
{
var handlers = this.DeviceRemoved;
if (handlers != null)
+ {
handlers(this, new DeviceEventArgs(device));
+ }
}
-
- #endregion
-
}
}
diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs
index 59a2710d5..bfad6de97 100644
--- a/RSSDP/SsdpDeviceLocator.cs
+++ b/RSSDP/SsdpDeviceLocator.cs
@@ -13,9 +13,6 @@ namespace Rssdp.Infrastructure
/// </summary>
public class SsdpDeviceLocator : DisposableManagedObjectBase
{
-
- #region Fields & Constants
-
private List<DiscoveredSsdpDevice> _Devices;
private ISsdpCommunicationsServer _CommunicationsServer;
@@ -25,16 +22,15 @@ namespace Rssdp.Infrastructure
private readonly TimeSpan DefaultSearchWaitTime = TimeSpan.FromSeconds(4);
private readonly TimeSpan OneSecond = TimeSpan.FromSeconds(1);
- #endregion
-
- #region Constructors
-
/// <summary>
/// Default constructor.
/// </summary>
public SsdpDeviceLocator(ISsdpCommunicationsServer communicationsServer)
{
- if (communicationsServer == null) throw new ArgumentNullException(nameof(communicationsServer));
+ if (communicationsServer == null)
+ {
+ throw new ArgumentNullException(nameof(communicationsServer));
+ }
_CommunicationsServer = communicationsServer;
_CommunicationsServer.ResponseReceived += CommsServer_ResponseReceived;
@@ -42,10 +38,6 @@ namespace Rssdp.Infrastructure
_Devices = new List<DiscoveredSsdpDevice>();
}
- #endregion
-
- #region Events
-
/// <summary>
/// Raised for when
/// <list type="bullet">
@@ -76,12 +68,6 @@ namespace Rssdp.Infrastructure
/// <seealso cref="StopListeningForNotifications"/>
public event EventHandler<DeviceUnavailableEventArgs> DeviceUnavailable;
- #endregion
-
- #region Public Methods
-
- #region Search Overloads
-
public void RestartBroadcastTimer(TimeSpan dueTime, TimeSpan period)
{
lock (_timerLock)
@@ -120,7 +106,6 @@ namespace Rssdp.Infrastructure
}
catch (Exception)
{
-
}
}
@@ -158,18 +143,31 @@ namespace Rssdp.Infrastructure
private Task SearchAsync(string searchTarget, TimeSpan searchWaitTime, CancellationToken cancellationToken)
{
- if (searchTarget == null) throw new ArgumentNullException(nameof(searchTarget));
- if (searchTarget.Length == 0) throw new ArgumentException("searchTarget cannot be an empty string.", nameof(searchTarget));
- if (searchWaitTime.TotalSeconds < 0) throw new ArgumentException("searchWaitTime must be a positive time.");
- if (searchWaitTime.TotalSeconds > 0 && searchWaitTime.TotalSeconds <= 1) throw new ArgumentException("searchWaitTime must be zero (if you are not using the result and relying entirely in the events), or greater than one second.");
+ if (searchTarget == null)
+ {
+ throw new ArgumentNullException(nameof(searchTarget));
+ }
+
+ if (searchTarget.Length == 0)
+ {
+ throw new ArgumentException("searchTarget cannot be an empty string.", nameof(searchTarget));
+ }
+
+ if (searchWaitTime.TotalSeconds < 0)
+ {
+ throw new ArgumentException("searchWaitTime must be a positive time.");
+ }
+
+ if (searchWaitTime.TotalSeconds > 0 && searchWaitTime.TotalSeconds <= 1)
+ {
+ throw new ArgumentException("searchWaitTime must be zero (if you are not using the result and relying entirely in the events), or greater than one second.");
+ }
ThrowIfDisposed();
return BroadcastDiscoverMessage(searchTarget, SearchTimeToMXValue(searchWaitTime), cancellationToken);
}
- #endregion
-
/// <summary>
/// Starts listening for broadcast notifications of service availability.
/// </summary>
@@ -212,14 +210,19 @@ namespace Rssdp.Infrastructure
/// <seealso cref="DeviceAvailable"/>
protected virtual void OnDeviceAvailable(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress localIpAddress)
{
- if (this.IsDisposed) return;
+ if (this.IsDisposed)
+ {
+ return;
+ }
var handlers = this.DeviceAvailable;
if (handlers != null)
+ {
handlers(this, new DeviceAvailableEventArgs(device, isNewDevice)
{
LocalIpAddress = localIpAddress
});
+ }
}
/// <summary>
@@ -230,17 +233,18 @@ namespace Rssdp.Infrastructure
/// <seealso cref="DeviceUnavailable"/>
protected virtual void OnDeviceUnavailable(DiscoveredSsdpDevice device, bool expired)
{
- if (this.IsDisposed) return;
+ if (this.IsDisposed)
+ {
+ return;
+ }
var handlers = this.DeviceUnavailable;
if (handlers != null)
+ {
handlers(this, new DeviceUnavailableEventArgs(device, expired));
+ }
}
- #endregion
-
- #region Public Properties
-
/// <summary>
/// Sets or returns a string containing the filter for notifications. Notifications not matching the filter will not raise the <see cref="ISsdpDeviceLocator.DeviceAvailable"/> or <see cref="ISsdpDeviceLocator.DeviceUnavailable"/> events.
/// </summary>
@@ -262,10 +266,6 @@ namespace Rssdp.Infrastructure
set;
}
- #endregion
-
- #region Overrides
-
/// <summary>
/// Disposes this object and all internal resources. Stops listening for all network messages.
/// </summary>
@@ -286,12 +286,6 @@ namespace Rssdp.Infrastructure
}
}
- #endregion
-
- #region Private Methods
-
- #region Discovery/Device Add
-
private void AddOrUpdateDiscoveredDevice(DiscoveredSsdpDevice device, IPAddress localIpAddress)
{
bool isNewDevice = false;
@@ -315,7 +309,10 @@ namespace Rssdp.Infrastructure
private void DeviceFound(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress localIpAddress)
{
- if (!NotificationTypeMatchesFilter(device)) return;
+ if (!NotificationTypeMatchesFilter(device))
+ {
+ return;
+ }
OnDeviceAvailable(device, isNewDevice, localIpAddress);
}
@@ -327,17 +324,13 @@ namespace Rssdp.Infrastructure
|| device.NotificationType == this.NotificationFilter;
}
- #endregion
-
- #region Network Message Processing
-
private Task BroadcastDiscoverMessage(string serviceType, TimeSpan mxValue, CancellationToken cancellationToken)
{
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
values["HOST"] = "239.255.255.250:1900";
values["USER-AGENT"] = "UPnP/1.0 DLNADOC/1.50 Platinum/1.0.4.2";
- //values["X-EMBY-SERVERID"] = _appHost.SystemId;
+ // values["X-EMBY-SERVERID"] = _appHost.SystemId;
values["MAN"] = "\"ssdp:discover\"";
@@ -356,8 +349,11 @@ namespace Rssdp.Infrastructure
private void ProcessSearchResponseMessage(HttpResponseMessage message, IPAddress localIpAddress)
{
- if (!message.IsSuccessStatusCode) return;
-
+ if (!message.IsSuccessStatusCode)
+ {
+ return;
+ }
+
var location = GetFirstHeaderUriValue("Location", message);
if (location != null)
{
@@ -377,13 +373,20 @@ namespace Rssdp.Infrastructure
private void ProcessNotificationMessage(HttpRequestMessage message, IPAddress localIpAddress)
{
- if (String.Compare(message.Method.Method, "Notify", StringComparison.OrdinalIgnoreCase) != 0) return;
+ if (String.Compare(message.Method.Method, "Notify", StringComparison.OrdinalIgnoreCase) != 0)
+ {
+ return;
+ }
var notificationType = GetFirstHeaderStringValue("NTS", message);
if (String.Compare(notificationType, SsdpConstants.SsdpKeepAliveNotification, StringComparison.OrdinalIgnoreCase) == 0)
+ {
ProcessAliveNotification(message, localIpAddress);
+ }
else if (String.Compare(notificationType, SsdpConstants.SsdpByeByeNotification, StringComparison.OrdinalIgnoreCase) == 0)
+ {
ProcessByeByeNotification(message);
+ }
}
private void ProcessAliveNotification(HttpRequestMessage message, IPAddress localIpAddress)
@@ -425,13 +428,13 @@ namespace Rssdp.Infrastructure
};
if (NotificationTypeMatchesFilter(deadDevice))
+ {
OnDeviceUnavailable(deadDevice, false);
+ }
}
}
}
- #region Header/Message Processing Utilities
-
private string GetFirstHeaderStringValue(string headerName, HttpResponseMessage message)
{
string retVal = null;
@@ -440,7 +443,9 @@ namespace Rssdp.Infrastructure
{
message.Headers.TryGetValues(headerName, out values);
if (values != null)
+ {
retVal = values.FirstOrDefault();
+ }
}
return retVal;
@@ -454,7 +459,9 @@ namespace Rssdp.Infrastructure
{
message.Headers.TryGetValues(headerName, out values);
if (values != null)
+ {
retVal = values.FirstOrDefault();
+ }
}
return retVal;
@@ -468,7 +475,9 @@ namespace Rssdp.Infrastructure
{
request.Headers.TryGetValues(headerName, out values);
if (values != null)
+ {
value = values.FirstOrDefault();
+ }
}
Uri retVal;
@@ -484,7 +493,9 @@ namespace Rssdp.Infrastructure
{
response.Headers.TryGetValues(headerName, out values);
if (values != null)
+ {
value = values.FirstOrDefault();
+ }
}
Uri retVal;
@@ -494,20 +505,20 @@ namespace Rssdp.Infrastructure
private TimeSpan CacheAgeFromHeader(System.Net.Http.Headers.CacheControlHeaderValue headerValue)
{
- if (headerValue == null) return TimeSpan.Zero;
+ if (headerValue == null)
+ {
+ return TimeSpan.Zero;
+ }
return (TimeSpan)(headerValue.MaxAge ?? headerValue.SharedMaxAge ?? TimeSpan.Zero);
}
- #endregion
-
- #endregion
-
- #region Expiry and Device Removal
-
private void RemoveExpiredDevicesFromCache()
{
- if (this.IsDisposed) return;
+ if (this.IsDisposed)
+ {
+ return;
+ }
DiscoveredSsdpDevice[] expiredDevices = null;
lock (_Devices)
@@ -516,7 +527,10 @@ namespace Rssdp.Infrastructure
foreach (var device in expiredDevices)
{
- if (this.IsDisposed) return;
+ if (this.IsDisposed)
+ {
+ return;
+ }
_Devices.Remove(device);
}
@@ -527,7 +541,10 @@ namespace Rssdp.Infrastructure
// problems.
foreach (var expiredUsn in (from expiredDevice in expiredDevices select expiredDevice.Usn).Distinct())
{
- if (this.IsDisposed) return;
+ if (this.IsDisposed)
+ {
+ return;
+ }
DeviceDied(expiredUsn, true);
}
@@ -541,7 +558,10 @@ namespace Rssdp.Infrastructure
existingDevices = FindExistingDeviceNotifications(_Devices, deviceUsn);
foreach (var existingDevice in existingDevices)
{
- if (this.IsDisposed) return true;
+ if (this.IsDisposed)
+ {
+ return true;
+ }
_Devices.Remove(existingDevice);
}
@@ -552,7 +572,9 @@ namespace Rssdp.Infrastructure
foreach (var removedDevice in existingDevices)
{
if (NotificationTypeMatchesFilter(removedDevice))
+ {
OnDeviceUnavailable(removedDevice, expired);
+ }
}
return true;
@@ -561,14 +583,16 @@ namespace Rssdp.Infrastructure
return false;
}
- #endregion
-
private TimeSpan SearchTimeToMXValue(TimeSpan searchWaitTime)
{
if (searchWaitTime.TotalSeconds < 2 || searchWaitTime == TimeSpan.Zero)
+ {
return OneSecond;
+ }
else
+ {
return searchWaitTime.Subtract(OneSecond);
+ }
}
private DiscoveredSsdpDevice FindExistingDeviceNotification(IEnumerable<DiscoveredSsdpDevice> devices, string notificationType, string usn)
@@ -580,6 +604,7 @@ namespace Rssdp.Infrastructure
return d;
}
}
+
return null;
}
@@ -598,10 +623,6 @@ namespace Rssdp.Infrastructure
return list;
}
- #endregion
-
- #region Event Handlers
-
private void CommsServer_ResponseReceived(object sender, ResponseReceivedEventArgs e)
{
ProcessSearchResponseMessage(e.Message, e.LocalIpAddress);
@@ -611,8 +632,5 @@ namespace Rssdp.Infrastructure
{
ProcessNotificationMessage(e.Message, e.LocalIpAddress);
}
-
- #endregion
-
}
}
diff --git a/RSSDP/SsdpDevicePublisher.cs b/RSSDP/SsdpDevicePublisher.cs
index 53b740052..1a8577d8d 100644
--- a/RSSDP/SsdpDevicePublisher.cs
+++ b/RSSDP/SsdpDevicePublisher.cs
@@ -40,12 +40,35 @@ namespace Rssdp.Infrastructure
public SsdpDevicePublisher(ISsdpCommunicationsServer communicationsServer, INetworkManager networkManager,
string osName, string osVersion, bool sendOnlyMatchedHost)
{
- if (communicationsServer == null) throw new ArgumentNullException(nameof(communicationsServer));
- if (networkManager == null) throw new ArgumentNullException(nameof(networkManager));
- if (osName == null) throw new ArgumentNullException(nameof(osName));
- if (osName.Length == 0) throw new ArgumentException("osName cannot be an empty string.", nameof(osName));
- if (osVersion == null) throw new ArgumentNullException(nameof(osVersion));
- if (osVersion.Length == 0) throw new ArgumentException("osVersion cannot be an empty string.", nameof(osName));
+ if (communicationsServer == null)
+ {
+ throw new ArgumentNullException(nameof(communicationsServer));
+ }
+
+ if (networkManager == null)
+ {
+ throw new ArgumentNullException(nameof(networkManager));
+ }
+
+ if (osName == null)
+ {
+ throw new ArgumentNullException(nameof(osName));
+ }
+
+ if (osName.Length == 0)
+ {
+ throw new ArgumentException("osName cannot be an empty string.", nameof(osName));
+ }
+
+ if (osVersion == null)
+ {
+ throw new ArgumentNullException(nameof(osVersion));
+ }
+
+ if (osVersion.Length == 0)
+ {
+ throw new ArgumentException("osVersion cannot be an empty string.", nameof(osName));
+ }
_SupportPnpRootDevice = true;
_Devices = new List<SsdpRootDevice>();
@@ -82,7 +105,10 @@ namespace Rssdp.Infrastructure
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "t", Justification = "Capture task to local variable supresses compiler warning, but task is not really needed.")]
public void AddDevice(SsdpRootDevice device)
{
- if (device == null) throw new ArgumentNullException(nameof(device));
+ if (device == null)
+ {
+ throw new ArgumentNullException(nameof(device));
+ }
ThrowIfDisposed();
@@ -115,7 +141,10 @@ namespace Rssdp.Infrastructure
/// <exception cref="ArgumentNullException">Thrown if the <paramref name="device"/> argument is null.</exception>
public async Task RemoveDevice(SsdpRootDevice device)
{
- if (device == null) throw new ArgumentNullException(nameof(device));
+ if (device == null)
+ {
+ throw new ArgumentNullException(nameof(device));
+ }
bool wasRemoved = false;
lock (_Devices)
@@ -156,6 +185,7 @@ namespace Rssdp.Infrastructure
public bool SupportPnpRootDevice
{
get { return _SupportPnpRootDevice; }
+
set
{
_SupportPnpRootDevice = value;
@@ -185,7 +215,9 @@ namespace Rssdp.Infrastructure
if (commsServer != null)
{
if (!commsServer.IsShared)
+ {
commsServer.Dispose();
+ }
}
_RecentSearchRequests = null;
@@ -205,53 +237,66 @@ namespace Rssdp.Infrastructure
return;
}
- //WriteTrace(String.Format("Search Request Received From {0}, Target = {1}", remoteEndPoint.ToString(), searchTarget));
+ // WriteTrace(String.Format("Search Request Received From {0}, Target = {1}", remoteEndPoint.ToString(), searchTarget));
if (IsDuplicateSearchRequest(searchTarget, remoteEndPoint))
{
- //WriteTrace("Search Request is Duplicate, ignoring.");
+ // WriteTrace("Search Request is Duplicate, ignoring.");
return;
}
- //Wait on random interval up to MX, as per SSDP spec.
- //Also, as per UPnP 1.1/SSDP spec ignore missing/bank MX header. If over 120, assume random value between 0 and 120.
- //Using 16 as minimum as that's often the minimum system clock frequency anyway.
+ // Wait on random interval up to MX, as per SSDP spec.
+ // Also, as per UPnP 1.1/SSDP spec ignore missing/bank MX header. If over 120, assume random value between 0 and 120.
+ // Using 16 as minimum as that's often the minimum system clock frequency anyway.
int maxWaitInterval = 0;
if (String.IsNullOrEmpty(mx))
{
- //Windows Explorer is poorly behaved and doesn't supply an MX header value.
- //if (this.SupportPnpRootDevice)
+ // Windows Explorer is poorly behaved and doesn't supply an MX header value.
+ // if (this.SupportPnpRootDevice)
mx = "1";
- //else
- //return;
+ // else
+ // return;
}
- if (!Int32.TryParse(mx, out maxWaitInterval) || maxWaitInterval <= 0) return;
+ if (!Int32.TryParse(mx, out maxWaitInterval) || maxWaitInterval <= 0)
+ {
+ return;
+ }
if (maxWaitInterval > 120)
+ {
maxWaitInterval = _Random.Next(0, 120);
+ }
- //Do not block synchronously as that may tie up a threadpool thread for several seconds.
+ // Do not block synchronously as that may tie up a threadpool thread for several seconds.
Task.Delay(_Random.Next(16, (maxWaitInterval * 1000))).ContinueWith((parentTask) =>
{
- //Copying devices to local array here to avoid threading issues/enumerator exceptions.
+ // Copying devices to local array here to avoid threading issues/enumerator exceptions.
IEnumerable<SsdpDevice> devices = null;
lock (_Devices)
{
if (String.Compare(SsdpConstants.SsdpDiscoverAllSTHeader, searchTarget, StringComparison.OrdinalIgnoreCase) == 0)
+ {
devices = GetAllDevicesAsFlatEnumerable().ToArray();
+ }
else if (String.Compare(SsdpConstants.UpnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0 || (this.SupportPnpRootDevice && String.Compare(SsdpConstants.PnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0))
+ {
devices = _Devices.ToArray();
+ }
else if (searchTarget.Trim().StartsWith("uuid:", StringComparison.OrdinalIgnoreCase))
+ {
devices = (from device in GetAllDevicesAsFlatEnumerable() where String.Compare(device.Uuid, searchTarget.Substring(5), StringComparison.OrdinalIgnoreCase) == 0 select device).ToArray();
+ }
else if (searchTarget.StartsWith("urn:", StringComparison.OrdinalIgnoreCase))
+ {
devices = (from device in GetAllDevicesAsFlatEnumerable() where String.Compare(device.FullDeviceType, searchTarget, StringComparison.OrdinalIgnoreCase) == 0 select device).ToArray();
+ }
}
if (devices != null)
{
var deviceList = devices.ToList();
- //WriteTrace(String.Format("Sending {0} search responses", deviceList.Count));
+ // WriteTrace(String.Format("Sending {0} search responses", deviceList.Count));
foreach (var device in deviceList)
{
@@ -264,7 +309,7 @@ namespace Rssdp.Infrastructure
}
else
{
- //WriteTrace(String.Format("Sending 0 search responses."));
+ // WriteTrace(String.Format("Sending 0 search responses."));
}
});
}
@@ -285,7 +330,9 @@ namespace Rssdp.Infrastructure
{
SendSearchResponse(SsdpConstants.UpnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), endPoint, receivedOnlocalIpAddress, cancellationToken);
if (this.SupportPnpRootDevice)
+ {
SendSearchResponse(SsdpConstants.PnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), endPoint, receivedOnlocalIpAddress, cancellationToken);
+ }
}
SendSearchResponse(device.Udn, device, device.Udn, endPoint, receivedOnlocalIpAddress, cancellationToken);
@@ -308,7 +355,7 @@ namespace Rssdp.Infrastructure
{
var rootDevice = device.ToRootDevice();
- //var additionalheaders = FormatCustomHeadersForResponse(device);
+ // var additionalheaders = FormatCustomHeadersForResponse(device);
const string header = "HTTP/1.1 200 OK";
@@ -335,10 +382,9 @@ namespace Rssdp.Infrastructure
}
catch (Exception)
{
-
}
- //WriteTrace(String.Format("Sent search response to " + endPoint.ToString()), device);
+ // WriteTrace(String.Format("Sent search response to " + endPoint.ToString()), device);
}
private bool IsDuplicateSearchRequest(string searchTarget, IPEndPoint endPoint)
@@ -352,15 +398,21 @@ namespace Rssdp.Infrastructure
{
var lastRequest = _RecentSearchRequests[newRequest.Key];
if (lastRequest.IsOld())
+ {
_RecentSearchRequests[newRequest.Key] = newRequest;
+ }
else
+ {
isDuplicateRequest = true;
+ }
}
else
{
_RecentSearchRequests.Add(newRequest.Key, newRequest);
if (_RecentSearchRequests.Count > 10)
+ {
CleanUpRecentSearchRequestsAsync();
+ }
}
}
@@ -382,9 +434,12 @@ namespace Rssdp.Infrastructure
{
try
{
- if (IsDisposed) return;
+ if (IsDisposed)
+ {
+ return;
+ }
- //WriteTrace("Begin Sending Alive Notifications For All Devices");
+ // WriteTrace("Begin Sending Alive Notifications For All Devices");
SsdpRootDevice[] devices;
lock (_Devices)
@@ -394,12 +449,15 @@ namespace Rssdp.Infrastructure
foreach (var device in devices)
{
- if (IsDisposed) return;
+ if (IsDisposed)
+ {
+ return;
+ }
SendAliveNotifications(device, true, CancellationToken.None);
}
- //WriteTrace("Completed Sending Alive Notifications For All Devices");
+ // WriteTrace("Completed Sending Alive Notifications For All Devices");
}
catch (ObjectDisposedException ex)
{
@@ -414,7 +472,9 @@ namespace Rssdp.Infrastructure
{
SendAliveNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), cancellationToken);
if (this.SupportPnpRootDevice)
+ {
SendAliveNotification(device, SsdpConstants.PnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), cancellationToken);
+ }
}
SendAliveNotification(device, device.Udn, device.Udn, cancellationToken);
@@ -448,7 +508,7 @@ namespace Rssdp.Infrastructure
_CommsServer.SendMulticastMessage(message, _sendOnlyMatchedHost ? rootDevice.Address : null, cancellationToken);
- //WriteTrace(String.Format("Sent alive notification"), device);
+ // WriteTrace(String.Format("Sent alive notification"), device);
}
private Task SendByeByeNotifications(SsdpDevice device, bool isRoot, CancellationToken cancellationToken)
@@ -458,7 +518,9 @@ namespace Rssdp.Infrastructure
{
tasks.Add(SendByeByeNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), cancellationToken));
if (this.SupportPnpRootDevice)
+ {
tasks.Add(SendByeByeNotification(device, "pnp:rootdevice", GetUsn(device.Udn, "pnp:rootdevice"), cancellationToken));
+ }
}
tasks.Add(SendByeByeNotification(device, device.Udn, device.Udn, cancellationToken));
@@ -499,20 +561,27 @@ namespace Rssdp.Infrastructure
var timer = _RebroadcastAliveNotificationsTimer;
_RebroadcastAliveNotificationsTimer = null;
if (timer != null)
+ {
timer.Dispose();
+ }
}
private TimeSpan GetMinimumNonZeroCacheLifetime()
{
- var nonzeroCacheLifetimesQuery = (from device
- in _Devices
- where device.CacheLifetime != TimeSpan.Zero
- select device.CacheLifetime).ToList();
+ var nonzeroCacheLifetimesQuery = (
+ from device
+ in _Devices
+ where device.CacheLifetime != TimeSpan.Zero
+ select device.CacheLifetime).ToList();
if (nonzeroCacheLifetimesQuery.Any())
+ {
return nonzeroCacheLifetimesQuery.Min();
+ }
else
+ {
return TimeSpan.Zero;
+ }
}
private string GetFirstHeaderValue(System.Net.Http.Headers.HttpRequestHeaders httpRequestHeaders, string headerName)
@@ -520,7 +589,9 @@ namespace Rssdp.Infrastructure
string retVal = null;
IEnumerable<String> values = null;
if (httpRequestHeaders.TryGetValues(headerName, out values) && values != null)
+ {
retVal = values.FirstOrDefault();
+ }
return retVal;
}
@@ -533,31 +604,38 @@ namespace Rssdp.Infrastructure
{
LogFunction(text);
}
- //System.Diagnostics.Debug.WriteLine(text, "SSDP Publisher");
+ // System.Diagnostics.Debug.WriteLine(text, "SSDP Publisher");
}
private void WriteTrace(string text, SsdpDevice device)
{
var rootDevice = device as SsdpRootDevice;
if (rootDevice != null)
+ {
WriteTrace(text + " " + device.DeviceType + " - " + device.Uuid + " - " + rootDevice.Location);
+ }
else
+ {
WriteTrace(text + " " + device.DeviceType + " - " + device.Uuid);
+ }
}
private void CommsServer_RequestReceived(object sender, RequestReceivedEventArgs e)
{
- if (this.IsDisposed) return;
+ if (this.IsDisposed)
+ {
+ return;
+ }
if (string.Equals(e.Message.Method.Method, SsdpConstants.MSearchMethod, StringComparison.OrdinalIgnoreCase))
{
- //According to SSDP/UPnP spec, ignore message if missing these headers.
+ // According to SSDP/UPnP spec, ignore message if missing these headers.
// Edit: But some devices do it anyway
- //if (!e.Message.Headers.Contains("MX"))
+ // if (!e.Message.Headers.Contains("MX"))
// WriteTrace("Ignoring search request - missing MX header.");
- //else if (!e.Message.Headers.Contains("MAN"))
+ // else if (!e.Message.Headers.Contains("MAN"))
// WriteTrace("Ignoring search request - missing MAN header.");
- //else
+ // else
ProcessSearchRequest(GetFirstHeaderValue(e.Message.Headers, "MX"), GetFirstHeaderValue(e.Message.Headers, "ST"), e.ReceivedFrom, e.LocalIpAddress, CancellationToken.None);
}
}
@@ -565,7 +643,9 @@ namespace Rssdp.Infrastructure
private class SearchRequest
{
public IPEndPoint EndPoint { get; set; }
+
public DateTime Received { get; set; }
+
public string SearchTarget { get; set; }
public string Key
diff --git a/RSSDP/SsdpEmbeddedDevice.cs b/RSSDP/SsdpEmbeddedDevice.cs
index 4810703d7..f1a598111 100644
--- a/RSSDP/SsdpEmbeddedDevice.cs
+++ b/RSSDP/SsdpEmbeddedDevice.cs
@@ -5,14 +5,8 @@ namespace Rssdp
/// </summary>
public class SsdpEmbeddedDevice : SsdpDevice
{
-
- #region Fields
private SsdpRootDevice _RootDevice;
- #endregion
-
- #region Constructors
-
/// <summary>
/// Default constructor.
/// </summary>
@@ -20,10 +14,6 @@ namespace Rssdp
{
}
- #endregion
-
- #region Public Properties
-
/// <summary>
/// Returns the <see cref="SsdpRootDevice"/> that is this device's first ancestor. If this device is itself an <see cref="SsdpRootDevice"/>, then returns a reference to itself.
/// </summary>
@@ -33,6 +23,7 @@ namespace Rssdp
{
return _RootDevice;
}
+
internal set
{
_RootDevice = value;
@@ -45,7 +36,5 @@ namespace Rssdp
}
}
}
-
- #endregion
}
}
diff --git a/RSSDP/SsdpRootDevice.cs b/RSSDP/SsdpRootDevice.cs
index 0f2de7b15..8937ec331 100644
--- a/RSSDP/SsdpRootDevice.cs
+++ b/RSSDP/SsdpRootDevice.cs
@@ -12,14 +12,8 @@ namespace Rssdp
/// </remarks>
public class SsdpRootDevice : SsdpDevice
{
- #region Fields
-
private Uri _UrlBase;
- #endregion
-
- #region Constructors
-
/// <summary>
/// Default constructor.
/// </summary>
@@ -27,10 +21,6 @@ namespace Rssdp
{
}
- #endregion
-
- #region Public Properties
-
/// <summary>
/// Specifies how long clients can cache this device's details for. Optional but defaults to <see cref="TimeSpan.Zero"/> which means no-caching. Recommended value is half an hour.
/// </summary>
@@ -77,7 +67,5 @@ namespace Rssdp
_UrlBase = value;
}
}
-
- #endregion
}
}
diff --git a/debian/changelog b/debian/changelog
index 35fb65957..d38d03805 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+jellyfin-server (10.6.0-2) unstable; urgency=medium
+
+ * Fix upgrade bug
+
+ -- Joshua Boniface <joshua@boniface.me> Sun, 19 Jul 22:47:27 -0400
+
jellyfin-server (10.6.0-1) unstable; urgency=medium
* Forthcoming stable release
diff --git a/debian/control b/debian/control
index 896d8286b..39c2aa055 100644
--- a/debian/control
+++ b/debian/control
@@ -15,9 +15,8 @@ Vcs-Git: https://github.org/jellyfin/jellyfin.git
Vcs-Browser: https://github.org/jellyfin/jellyfin
Package: jellyfin-server
-Replaces: mediabrowser, emby, emby-server-beta, jellyfin-dev, emby-server
-Breaks: mediabrowser, emby, emby-server-beta, jellyfin-dev, emby-server
-Conflicts: mediabrowser, emby, emby-server-beta, jellyfin-dev, emby-server
+Replaces: jellyfin (<<10.6.0)
+Breaks: jellyfin (<<10.6.0)
Architecture: any
Depends: at,
libsqlite3-0,
diff --git a/deployment/Dockerfile.docker.amd64 b/deployment/Dockerfile.docker.amd64
new file mode 100644
index 000000000..204ded3a4
--- /dev/null
+++ b/deployment/Dockerfile.docker.amd64
@@ -0,0 +1,15 @@
+ARG DOTNET_VERSION=3.1
+
+FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster
+
+ARG SOURCE_DIR=/src
+ARG ARTIFACT_DIR=/jellyfin
+
+WORKDIR ${SOURCE_DIR}
+COPY . .
+
+ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
+
+# because of changes in docker and systemd we need to not build in parallel at the moment
+# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
diff --git a/deployment/Dockerfile.docker.arm64 b/deployment/Dockerfile.docker.arm64
new file mode 100644
index 000000000..eedbaac33
--- /dev/null
+++ b/deployment/Dockerfile.docker.arm64
@@ -0,0 +1,15 @@
+ARG DOTNET_VERSION=3.1
+
+FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster
+
+ARG SOURCE_DIR=/src
+ARG ARTIFACT_DIR=/jellyfin
+
+WORKDIR ${SOURCE_DIR}
+COPY . .
+
+ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
+
+# because of changes in docker and systemd we need to not build in parallel at the moment
+# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
diff --git a/deployment/Dockerfile.docker.armhf b/deployment/Dockerfile.docker.armhf
new file mode 100644
index 000000000..2a500246b
--- /dev/null
+++ b/deployment/Dockerfile.docker.armhf
@@ -0,0 +1,15 @@
+ARG DOTNET_VERSION=3.1
+
+FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster
+
+ARG SOURCE_DIR=/src
+ARG ARTIFACT_DIR=/jellyfin
+
+WORKDIR ${SOURCE_DIR}
+COPY . .
+
+ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
+
+# because of changes in docker and systemd we need to not build in parallel at the moment
+# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
diff --git a/deployment/build.centos.amd64 b/deployment/build.centos.amd64
index 939bbc45a..69f0cadcf 100755
--- a/deployment/build.centos.amd64
+++ b/deployment/build.centos.amd64
@@ -8,6 +8,22 @@ set -o xtrace
# Move to source directory
pushd ${SOURCE_DIR}
+# Modify changelog to unstable configuration if IS_UNSTABLE
+if [[ ${IS_UNSTABLE} == 'yes' ]]; then
+ pushd fedora
+
+ PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' )
+
+ sed -i "s/Version:.*/Version: ${BUILD_ID}/" jellyfin.spec
+ sed -i "/%changelog/q" jellyfin.spec
+
+ cat <<EOF >>jellyfin.spec
+* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team <packaging@jellyfin.org>
+- Jellyfin Server unstable build ${BUILD_ID} for merged PR #${PR_ID}
+EOF
+ popd
+fi
+
# Build RPM
make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS
rpmbuild --rebuild -bb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm
diff --git a/deployment/build.debian.amd64 b/deployment/build.debian.amd64
index f44c6a7d1..012e1cebf 100755
--- a/deployment/build.debian.amd64
+++ b/deployment/build.debian.amd64
@@ -14,6 +14,21 @@ if [[ ${IS_DOCKER} == YES ]]; then
sed -i '/dotnet-sdk-3.1,/d' debian/control
fi
+# Modify changelog to unstable configuration if IS_UNSTABLE
+if [[ ${IS_UNSTABLE} == 'yes' ]]; then
+ pushd debian
+ PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' )
+
+ cat <<EOF >changelog
+jellyfin-server (${BUILD_ID}-unstable) unstable; urgency=medium
+
+ * Jellyfin Server unstable build ${BUILD_ID} for merged PR #${PR_ID}
+
+ -- Jellyfin Packaging Team <packaging@jellyfin.org> $( date --rfc-2822 )
+EOF
+ popd
+fi
+
# Build DEB
dpkg-buildpackage -us -uc --pre-clean --post-clean
diff --git a/deployment/build.debian.arm64 b/deployment/build.debian.arm64
index 0127671f3..12ce3e874 100755
--- a/deployment/build.debian.arm64
+++ b/deployment/build.debian.arm64
@@ -14,6 +14,21 @@ if [[ ${IS_DOCKER} == YES ]]; then
sed -i '/dotnet-sdk-3.1,/d' debian/control
fi
+# Modify changelog to unstable configuration if IS_UNSTABLE
+if [[ ${IS_UNSTABLE} == 'yes' ]]; then
+ pushd debian
+ PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' )
+
+ cat <<EOF >changelog
+jellyfin-server (${BUILD_ID}-unstable) unstable; urgency=medium
+
+ * Jellyfin Server unstable build ${BUILD_ID} for merged PR #${PR_ID}
+
+ -- Jellyfin Packaging Team <packaging@jellyfin.org> $( date --rfc-2822 )
+EOF
+ popd
+fi
+
# Build DEB
export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
dpkg-buildpackage -us -uc -a arm64 --pre-clean --post-clean
diff --git a/deployment/build.debian.armhf b/deployment/build.debian.armhf
index 02e3db4fc..3089eab58 100755
--- a/deployment/build.debian.armhf
+++ b/deployment/build.debian.armhf
@@ -14,6 +14,21 @@ if [[ ${IS_DOCKER} == YES ]]; then
sed -i '/dotnet-sdk-3.1,/d' debian/control
fi
+# Modify changelog to unstable configuration if IS_UNSTABLE
+if [[ ${IS_UNSTABLE} == 'yes' ]]; then
+ pushd debian
+ PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' )
+
+ cat <<EOF >changelog
+jellyfin-server (${BUILD_ID}-unstable) unstable; urgency=medium
+
+ * Jellyfin Server unstable build ${BUILD_ID} for merged PR #${PR_ID}
+
+ -- Jellyfin Packaging Team <packaging@jellyfin.org> $( date --rfc-2822 )
+EOF
+ popd
+fi
+
# Build DEB
export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
dpkg-buildpackage -us -uc -a armhf --pre-clean --post-clean
diff --git a/deployment/build.fedora.amd64 b/deployment/build.fedora.amd64
index 8ac99decc..2c7bff506 100755
--- a/deployment/build.fedora.amd64
+++ b/deployment/build.fedora.amd64
@@ -8,6 +8,22 @@ set -o xtrace
# Move to source directory
pushd ${SOURCE_DIR}
+# Modify changelog to unstable configuration if IS_UNSTABLE
+if [[ ${IS_UNSTABLE} == 'yes' ]]; then
+ pushd fedora
+
+ PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' )
+
+ sed -i "s/Version:.*/Version: ${BUILD_ID}/" jellyfin.spec
+ sed -i "/%changelog/q" jellyfin.spec
+
+ cat <<EOF >>jellyfin.spec
+* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team <packaging@jellyfin.org>
+- Jellyfin Server unstable build ${BUILD_ID} for merged PR #${PR_ID}
+EOF
+ popd
+fi
+
# Build RPM
make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS
rpmbuild -rb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm
diff --git a/deployment/build.linux.amd64 b/deployment/build.linux.amd64
index 0cbbd05cf..a7fb0544a 100755
--- a/deployment/build.linux.amd64
+++ b/deployment/build.linux.amd64
@@ -9,7 +9,11 @@ set -o xtrace
pushd ${SOURCE_DIR}
# Get version
-version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
+if [[ ${IS_UNSTABLE} == 'yes' ]]; then
+ version="${BUILD_ID}"
+else
+ version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
+fi
# Build archives
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
diff --git a/deployment/build.macos b/deployment/build.macos
index 16be29eee..d808141ac 100755
--- a/deployment/build.macos
+++ b/deployment/build.macos
@@ -9,7 +9,11 @@ set -o xtrace
pushd ${SOURCE_DIR}
# Get version
-version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
+if [[ ${IS_UNSTABLE} == 'yes' ]]; then
+ version="${BUILD_ID}"
+else
+ version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
+fi
# Build archives
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
diff --git a/deployment/build.portable b/deployment/build.portable
index 1e8a4ab62..24a8cbf32 100755
--- a/deployment/build.portable
+++ b/deployment/build.portable
@@ -9,7 +9,11 @@ set -o xtrace
pushd ${SOURCE_DIR}
# Get version
-version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
+if [[ ${IS_UNSTABLE} == 'yes' ]]; then
+ version="${BUILD_ID}"
+else
+ version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
+fi
# Build archives
dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
diff --git a/deployment/build.ubuntu.amd64 b/deployment/build.ubuntu.amd64
index 107ddbe02..0eac9cdd1 100755
--- a/deployment/build.ubuntu.amd64
+++ b/deployment/build.ubuntu.amd64
@@ -14,6 +14,21 @@ if [[ ${IS_DOCKER} == YES ]]; then
sed -i '/dotnet-sdk-3.1,/d' debian/control
fi
+# Modify changelog to unstable configuration if IS_UNSTABLE
+if [[ ${IS_UNSTABLE} == 'yes' ]]; then
+ pushd debian
+ PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' )
+
+ cat <<EOF >changelog
+jellyfin-server (${BUILD_ID}-unstable) unstable; urgency=medium
+
+ * Jellyfin Server unstable build ${BUILD_ID} for merged PR #${PR_ID}
+
+ -- Jellyfin Packaging Team <packaging@jellyfin.org> $( date --rfc-2822 )
+EOF
+ popd
+fi
+
# Build DEB
dpkg-buildpackage -us -uc --pre-clean --post-clean
diff --git a/deployment/build.ubuntu.arm64 b/deployment/build.ubuntu.arm64
index b13868f44..5b11fd543 100755
--- a/deployment/build.ubuntu.arm64
+++ b/deployment/build.ubuntu.arm64
@@ -14,6 +14,21 @@ if [[ ${IS_DOCKER} == YES ]]; then
sed -i '/dotnet-sdk-3.1,/d' debian/control
fi
+# Modify changelog to unstable configuration if IS_UNSTABLE
+if [[ ${IS_UNSTABLE} == 'yes' ]]; then
+ pushd debian
+ PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' )
+
+ cat <<EOF >changelog
+jellyfin-server (${BUILD_ID}-unstable) unstable; urgency=medium
+
+ * Jellyfin Server unstable build ${BUILD_ID} for merged PR #${PR_ID}
+
+ -- Jellyfin Packaging Team <packaging@jellyfin.org> $( date --rfc-2822 )
+EOF
+ popd
+fi
+
# Build DEB
export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
dpkg-buildpackage -us -uc -a arm64 --pre-clean --post-clean
diff --git a/deployment/build.ubuntu.armhf b/deployment/build.ubuntu.armhf
index 0b4dd308a..4734cf658 100755
--- a/deployment/build.ubuntu.armhf
+++ b/deployment/build.ubuntu.armhf
@@ -14,6 +14,21 @@ if [[ ${IS_DOCKER} == YES ]]; then
sed -i '/dotnet-sdk-3.1,/d' debian/control
fi
+# Modify changelog to unstable configuration if IS_UNSTABLE
+if [[ ${IS_UNSTABLE} == 'yes' ]]; then
+ pushd debian
+ PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' )
+
+ cat <<EOF >changelog
+jellyfin-server (${BUILD_ID}-unstable) unstable; urgency=medium
+
+ * Jellyfin Server unstable build ${BUILD_ID} for merged PR #${PR_ID}
+
+ -- Jellyfin Packaging Team <packaging@jellyfin.org> $( date --rfc-2822 )
+EOF
+ popd
+fi
+
# Build DEB
export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
dpkg-buildpackage -us -uc -a armhf --pre-clean --post-clean
diff --git a/deployment/build.windows.amd64 b/deployment/build.windows.amd64
index 39bd41f99..bd5dc6438 100755
--- a/deployment/build.windows.amd64
+++ b/deployment/build.windows.amd64
@@ -8,14 +8,17 @@ set -o xtrace
# Version variables
NSSM_VERSION="nssm-2.24-101-g897c7ad"
NSSM_URL="http://files.evilt.win/nssm/${NSSM_VERSION}.zip"
-FFMPEG_VERSION="ffmpeg-4.2.1-win64-static"
-FFMPEG_URL="https://ffmpeg.zeranoe.com/builds/win64/static/${FFMPEG_VERSION}.zip"
+FFMPEG_URL="https://repo.jellyfin.org/releases/server/windows/ffmpeg/jellyfin-ffmpeg.zip";
# Move to source directory
pushd ${SOURCE_DIR}
# Get version
-version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
+if [[ ${IS_UNSTABLE} == 'yes' ]]; then
+ version="${BUILD_ID}"
+else
+ version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
+fi
output_dir="dist/jellyfin-server_${version}"
@@ -25,12 +28,11 @@ dotnet publish Jellyfin.Server --configuration Release --self-contained --runtim
# Prepare addins
addin_build_dir="$( mktemp -d )"
wget ${NSSM_URL} -O ${addin_build_dir}/nssm.zip
-wget ${FFMPEG_URL} -O ${addin_build_dir}/ffmpeg.zip
+wget ${FFMPEG_URL} -O ${addin_build_dir}/jellyfin-ffmpeg.zip
unzip ${addin_build_dir}/nssm.zip -d ${addin_build_dir}
cp ${addin_build_dir}/${NSSM_VERSION}/win64/nssm.exe ${output_dir}/nssm.exe
-unzip ${addin_build_dir}/ffmpeg.zip -d ${addin_build_dir}
-cp ${addin_build_dir}/${FFMPEG_VERSION}/bin/ffmpeg.exe ${output_dir}/ffmpeg.exe
-cp ${addin_build_dir}/${FFMPEG_VERSION}/bin/ffprobe.exe ${output_dir}/ffprobe.exe
+unzip ${addin_build_dir}/jellyfin-ffmpeg.zip -d ${addin_build_dir}/jellyfin-ffmpeg
+cp ${addin_build_dir}/jellyfin-ffmpeg/* ${output_dir}
rm -rf ${addin_build_dir}
# Prepare scripts
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index 010fad520..05bb36832 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -13,15 +13,15 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="AutoFixture" Version="4.11.0" />
- <PackageReference Include="AutoFixture.AutoMoq" Version="4.11.0" />
- <PackageReference Include="AutoFixture.Xunit2" Version="4.11.0" />
- <PackageReference Include="Microsoft.Extensions.Options" Version="3.1.5" />
+ <PackageReference Include="AutoFixture" Version="4.13.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.12.0" />
+ <PackageReference Include="AutoFixture.Xunit2" Version="4.12.0" />
+ <PackageReference Include="Microsoft.Extensions.Options" Version="3.1.6" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.2.1" />
- <PackageReference Include="Moq" Version="4.14.1" />
+ <PackageReference Include="coverlet.collector" Version="1.3.0" />
+ <PackageReference Include="Moq" Version="4.14.5" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
index 73af10065..4cb1da994 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -16,7 +16,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.2.1" />
+ <PackageReference Include="coverlet.collector" Version="1.3.0" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
index 5c7934fe3..18724f31c 100644
--- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
+++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
@@ -16,7 +16,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.2.1" />
+ <PackageReference Include="coverlet.collector" Version="1.3.0" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs
index af29fec87..9eb601edf 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs
@@ -17,6 +17,7 @@ namespace Jellyfin.MediaEncoding.Tests
}
[Theory]
+ [InlineData(EncoderValidatorTestsData.FFmpegV43Output, true)]
[InlineData(EncoderValidatorTestsData.FFmpegV421Output, true)]
[InlineData(EncoderValidatorTestsData.FFmpegV42Output, true)]
[InlineData(EncoderValidatorTestsData.FFmpegV414Output, true)]
@@ -32,6 +33,7 @@ namespace Jellyfin.MediaEncoding.Tests
{
public IEnumerator<object?[]> GetEnumerator()
{
+ yield return new object?[] { EncoderValidatorTestsData.FFmpegV43Output, new Version(4, 3) };
yield return new object?[] { EncoderValidatorTestsData.FFmpegV421Output, new Version(4, 2, 1) };
yield return new object?[] { EncoderValidatorTestsData.FFmpegV42Output, new Version(4, 2) };
yield return new object?[] { EncoderValidatorTestsData.FFmpegV414Output, new Version(4, 1, 4) };
diff --git a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs
index c46c9578b..f5ff3d723 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs
@@ -2,6 +2,18 @@ namespace Jellyfin.MediaEncoding.Tests
{
internal static class EncoderValidatorTestsData
{
+ public const string FFmpegV43Output = @"ffmpeg version 4.3 Copyright (c) 2000-2020 the FFmpeg developers
+built with gcc 7 (Ubuntu 7.5.0-3ubuntu1~18.04)
+configuration: --prefix=/usr/lib/jellyfin-ffmpeg --target-os=linux --disable-doc --disable-ffplay --disable-shared --disable-libxcb --disable-vdpau --disable-sdl2 --disable-xlib --enable-gpl --enable-version3 --enable-static --enable-libfontconfig --enable-fontconfig --enable-gmp --enable-gnutls --enable-libass --enable-libbluray --enable-libdrm --enable-libfreetype --enable-libfribidi --enable-libmp3lame --enable-libopus --enable-libtheora --enable-libvorbis --enable-libwebp --enable-libx264 --enable-libx265 --enable-libzvbi --arch=amd64 --enable-amf --enable-nvenc --enable-nvdec --enable-vaapi --enable-opencl
+libavutil 56. 51.100 / 56. 51.100
+libavcodec 58. 91.100 / 58. 91.100
+libavformat 58. 45.100 / 58. 45.100
+libavdevice 58. 10.100 / 58. 10.100
+libavfilter 7. 85.100 / 7. 85.100
+libswscale 5. 7.100 / 5. 7.100
+libswresample 3. 7.100 / 3. 7.100
+libpostproc 55. 7.100 / 55. 7.100";
+
public const string FFmpegV421Output = @"ffmpeg version 4.2.1 Copyright (c) 2000-2019 the FFmpeg developers
built with gcc 9.1.1 (GCC) 20190807
configuration: --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth --enable-libopenmpt
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
index 74fc37e49..646ef00fd 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
@@ -22,7 +22,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.2.1" />
+ <PackageReference Include="coverlet.collector" Version="1.3.0" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
index 355949640..1434cce96 100644
--- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
+++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
@@ -16,7 +16,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.2.1" />
+ <PackageReference Include="coverlet.collector" Version="1.3.0" />
</ItemGroup>
<ItemGroup>
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
index 83aca38b4..03187f4b9 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -14,12 +14,12 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="AutoFixture" Version="4.11.0" />
- <PackageReference Include="AutoFixture.AutoMoq" Version="4.11.0" />
- <PackageReference Include="Moq" Version="4.14.1" />
+ <PackageReference Include="AutoFixture" Version="4.13.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.12.0" />
+ <PackageReference Include="Moq" Version="4.14.5" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.2.1" />
+ <PackageReference Include="coverlet.collector" Version="1.3.0" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs
index 26dee38c6..c145ddc9d 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs
@@ -7,12 +7,19 @@ namespace Jellyfin.Server.Implementations.Tests.Library
{
[Theory]
[InlineData("/media/small.jpg", true)]
+ [InlineData("/media/albumart.jpg", true)]
+ [InlineData("/media/movie.sample.mp4", true)]
[InlineData("/media/movies/#Recycle/test.txt", true)]
[InlineData("/media/movies/#recycle/", true)]
+ [InlineData("/media/movies/#recycle", true)]
[InlineData("thumbs.db", true)]
[InlineData(@"C:\media\movies\movie.avi", false)]
[InlineData("/media/.hiddendir/file.mp4", true)]
[InlineData("/media/dir/.hiddenfile.mp4", true)]
+ [InlineData("/volume1/video/Series/@eaDir", true)]
+ [InlineData("/volume1/video/Series/@eaDir/file.txt", true)]
+ [InlineData("/directory/@Recycle", true)]
+ [InlineData("/directory/@Recycle/file.mp3", true)]
public void PathIgnored(string path, bool expected)
{
Assert.Equal(expected, IgnorePatterns.ShouldIgnore(path));
diff --git a/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj b/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj
index 597053131..8309faebd 100644
--- a/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj
+++ b/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj
@@ -8,11 +8,11 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.5" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.6" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.2.1" />
+ <PackageReference Include="coverlet.collector" Version="1.3.0" />
</ItemGroup>
<ItemGroup>
diff --git a/windows/build-jellyfin.ps1 b/windows/build-jellyfin.ps1
index c762137a7..b76a8e0bb 100644
--- a/windows/build-jellyfin.ps1
+++ b/windows/build-jellyfin.ps1
@@ -47,7 +47,7 @@ function Install-FFMPEG {
param(
[string]$ResolvedInstallLocation,
[string]$Architecture,
- [string]$FFMPEGVersionX86 = "ffmpeg-4.2.1-win32-shared"
+ [string]$FFMPEGVersionX86 = "ffmpeg-4.3-win32-shared"
)
Write-Verbose "Checking Architecture"
if($Architecture -notin @('x86','x64')){