aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ci/azure-pipelines-package.yml2
-rw-r--r--.github/workflows/codeql-analysis.yml8
-rw-r--r--.github/workflows/commands.yml2
-rw-r--r--.github/workflows/openapi.yml12
-rw-r--r--.github/workflows/repo-stale.yaml2
-rw-r--r--CONTRIBUTORS.md4
-rw-r--r--Dockerfile6
-rw-r--r--Dockerfile.arm5
-rw-r--r--Dockerfile.arm642
-rw-r--r--Emby.Dlna/Configuration/DlnaOptions.cs2
-rw-r--r--Emby.Dlna/ContentDirectory/ContentDirectoryService.cs5
-rw-r--r--Emby.Dlna/ContentDirectory/ControlHandler.cs10
-rw-r--r--Emby.Dlna/Didl/DidlBuilder.cs4
-rw-r--r--Emby.Dlna/DlnaManager.cs126
-rw-r--r--Emby.Dlna/Emby.Dlna.csproj2
-rw-r--r--Emby.Dlna/IDlnaEventManager.cs4
-rw-r--r--Emby.Dlna/Main/DlnaEntryPoint.cs2
-rw-r--r--Emby.Dlna/PlayTo/Device.cs48
-rw-r--r--Emby.Dlna/PlayTo/DlnaHttpClient.cs108
-rw-r--r--Emby.Dlna/PlayTo/PlayToController.cs1
-rw-r--r--Emby.Dlna/PlayTo/SsdpHttpClient.cs141
-rw-r--r--Emby.Dlna/PlayTo/TransportCommands.cs5
-rw-r--r--Emby.Dlna/PlayTo/UpnpContainer.cs5
-rw-r--r--Emby.Dlna/PlayTo/uBaseObject.cs5
-rw-r--r--Emby.Dlna/Profiles/DefaultProfile.cs14
-rw-r--r--Emby.Dlna/Profiles/DenonAvrProfile.cs34
-rw-r--r--Emby.Dlna/Profiles/DirectTvProfile.cs129
-rw-r--r--Emby.Dlna/Profiles/DishHopperJoeyProfile.cs228
-rw-r--r--Emby.Dlna/Profiles/Foobar2000Profile.cs78
-rw-r--r--Emby.Dlna/Profiles/LgTvProfile.cs211
-rw-r--r--Emby.Dlna/Profiles/LinksysDMA2100Profile.cs55
-rw-r--r--Emby.Dlna/Profiles/MarantzProfile.cs43
-rw-r--r--Emby.Dlna/Profiles/MediaMonkeyProfile.cs44
-rw-r--r--Emby.Dlna/Profiles/PanasonicVieraProfile.cs222
-rw-r--r--Emby.Dlna/Profiles/PopcornHourProfile.cs225
-rw-r--r--Emby.Dlna/Profiles/SamsungSmartTvProfile.cs367
-rw-r--r--Emby.Dlna/Profiles/SharpSmartTvProfile.cs121
-rw-r--r--Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs230
-rw-r--r--Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs230
-rw-r--r--Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs218
-rw-r--r--Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs218
-rw-r--r--Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs284
-rw-r--r--Emby.Dlna/Profiles/SonyBravia2010Profile.cs366
-rw-r--r--Emby.Dlna/Profiles/SonyBravia2011Profile.cs389
-rw-r--r--Emby.Dlna/Profiles/SonyBravia2012Profile.cs307
-rw-r--r--Emby.Dlna/Profiles/SonyBravia2013Profile.cs324
-rw-r--r--Emby.Dlna/Profiles/SonyBravia2014Profile.cs324
-rw-r--r--Emby.Dlna/Profiles/SonyPs3Profile.cs269
-rw-r--r--Emby.Dlna/Profiles/SonyPs4Profile.cs278
-rw-r--r--Emby.Dlna/Profiles/WdtvLiveProfile.cs266
-rw-r--r--Emby.Dlna/Profiles/XboxOneProfile.cs372
-rw-r--r--Emby.Dlna/Service/BaseControlHandler.cs2
-rw-r--r--Emby.Drawing/Emby.Drawing.csproj2
-rw-r--r--Emby.Drawing/ImageProcessor.cs83
-rw-r--r--Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs2
-rw-r--r--Emby.Naming/Common/NamingOptions.cs3
-rw-r--r--Emby.Naming/Emby.Naming.csproj4
-rw-r--r--Emby.Notifications/Emby.Notifications.csproj2
-rw-r--r--Emby.Photos/Emby.Photos.csproj4
-rw-r--r--Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs23
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs64
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs5
-rw-r--r--Emby.Server.Implementations/Data/SqliteExtensions.cs5
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs207
-rw-r--r--Emby.Server.Implementations/Data/SqliteUserDataRepository.cs15
-rw-r--r--Emby.Server.Implementations/Data/SynchronousMode.cs (renamed from Emby.Server.Implementations/Data/SynchronouseMode.cs)0
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs10
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj12
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/SessionContext.cs60
-rw-r--r--Emby.Server.Implementations/HttpServer/WebSocketConnection.cs34
-rw-r--r--Emby.Server.Implementations/IO/ManagedFileSystem.cs4
-rw-r--r--Emby.Server.Implementations/Images/DynamicImageProvider.cs1
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs81
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs11
-rw-r--r--Emby.Server.Implementations/Library/MediaStreamSelector.cs4
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs8
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs5
-rw-r--r--Emby.Server.Implementations/Library/SearchEngine.cs2
-rw-r--r--Emby.Server.Implementations/Library/UserDataManager.cs15
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs12
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs10
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs10
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs11
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs18
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs27
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs7
-rw-r--r--Emby.Server.Implementations/Localization/Core/af.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/ar.json69
-rw-r--r--Emby.Server.Implementations/Localization/Core/bg-BG.json7
-rw-r--r--Emby.Server.Implementations/Localization/Core/ca.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/de.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/el.json26
-rw-r--r--Emby.Server.Implementations/Localization/Core/es-AR.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/es-MX.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/es.json10
-rw-r--r--Emby.Server.Implementations/Localization/Core/es_419.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/es_DO.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/et.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/fi.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/fr-CA.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/fr.json33
-rw-r--r--Emby.Server.Implementations/Localization/Core/gsw.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/he.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/hi.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/hr.json7
-rw-r--r--Emby.Server.Implementations/Localization/Core/id.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/it.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/ja.json10
-rw-r--r--Emby.Server.Implementations/Localization/Core/kab.json14
-rw-r--r--Emby.Server.Implementations/Localization/Core/ko.json9
-rw-r--r--Emby.Server.Implementations/Localization/Core/lt-LT.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/lv.json7
-rw-r--r--Emby.Server.Implementations/Localization/Core/mk.json28
-rw-r--r--Emby.Server.Implementations/Localization/Core/mr.json24
-rw-r--r--Emby.Server.Implementations/Localization/Core/ms.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/my.json137
-rw-r--r--Emby.Server.Implementations/Localization/Core/nb.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/pt-PT.json11
-rw-r--r--Emby.Server.Implementations/Localization/Core/pt.json21
-rw-r--r--Emby.Server.Implementations/Localization/Core/ru.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/sl-SI.json7
-rw-r--r--Emby.Server.Implementations/Localization/Core/sr.json9
-rw-r--r--Emby.Server.Implementations/Localization/Core/sv.json40
-rw-r--r--Emby.Server.Implementations/Localization/Core/th.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/ug.json119
-rw-r--r--Emby.Server.Implementations/Localization/Core/uk.json16
-rw-r--r--Emby.Server.Implementations/Localization/Core/uz.json12
-rw-r--r--Emby.Server.Implementations/Localization/Core/vi.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-CN.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-HK.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-TW.json52
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/fi.csv10
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/no.csv6
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/se.csv5
-rw-r--r--Emby.Server.Implementations/Net/SocketFactory.cs5
-rw-r--r--Emby.Server.Implementations/Net/UdpSocket.cs14
-rw-r--r--Emby.Server.Implementations/Plugins/PluginManager.cs20
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs30
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs56
-rw-r--r--Emby.Server.Implementations/Session/SessionWebSocketListener.cs19
-rw-r--r--Emby.Server.Implementations/Session/WebSocketController.cs23
-rw-r--r--Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs10
-rw-r--r--Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs10
-rw-r--r--Emby.Server.Implementations/Sorting/DateCreatedComparer.cs10
-rw-r--r--Emby.Server.Implementations/Sorting/IndexNumberComparer.cs11
-rw-r--r--Emby.Server.Implementations/Sorting/NameComparer.cs10
-rw-r--r--Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs12
-rw-r--r--Emby.Server.Implementations/Sorting/ParentIndexNumberComparer.cs11
-rw-r--r--Emby.Server.Implementations/Sorting/RuntimeComparer.cs10
-rw-r--r--Emby.Server.Implementations/Sorting/SortNameComparer.cs10
-rw-r--r--Emby.Server.Implementations/Sorting/StudioComparer.cs11
-rw-r--r--Emby.Server.Implementations/TV/TVSeriesManager.cs53
-rw-r--r--Emby.Server.Implementations/Updates/InstallationManager.cs5
-rw-r--r--Jellyfin.Api/Attributes/DlnaEnabledAttribute.cs25
-rw-r--r--Jellyfin.Api/Attributes/HttpSubscribeAttribute.cs7
-rw-r--r--Jellyfin.Api/Attributes/HttpUnsubscribeAttribute.cs7
-rw-r--r--Jellyfin.Api/Auth/BaseAuthorizationHandler.cs9
-rw-r--r--Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs9
-rw-r--r--Jellyfin.Api/BaseJellyfinApiController.cs37
-rw-r--r--Jellyfin.Api/Controllers/ArtistsController.cs6
-rw-r--r--Jellyfin.Api/Controllers/AudioController.cs2
-rw-r--r--Jellyfin.Api/Controllers/ClientLogController.cs7
-rw-r--r--Jellyfin.Api/Controllers/CollectionController.cs11
-rw-r--r--Jellyfin.Api/Controllers/ConfigurationController.cs11
-rw-r--r--Jellyfin.Api/Controllers/DisplayPreferencesController.cs7
-rw-r--r--Jellyfin.Api/Controllers/DlnaServerController.cs99
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs57
-rw-r--r--Jellyfin.Api/Controllers/GenresController.cs4
-rw-r--r--Jellyfin.Api/Controllers/ImageController.cs56
-rw-r--r--Jellyfin.Api/Controllers/InstantMixController.cs14
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs123
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs37
-rw-r--r--Jellyfin.Api/Controllers/LiveTvController.cs39
-rw-r--r--Jellyfin.Api/Controllers/MediaInfoController.cs17
-rw-r--r--Jellyfin.Api/Controllers/MoviesController.cs4
-rw-r--r--Jellyfin.Api/Controllers/MusicGenresController.cs4
-rw-r--r--Jellyfin.Api/Controllers/PackageController.cs2
-rw-r--r--Jellyfin.Api/Controllers/PersonsController.cs9
-rw-r--r--Jellyfin.Api/Controllers/PlaylistsController.cs2
-rw-r--r--Jellyfin.Api/Controllers/PlaystateController.cs28
-rw-r--r--Jellyfin.Api/Controllers/QuickConnectController.cs15
-rw-r--r--Jellyfin.Api/Controllers/SearchController.cs17
-rw-r--r--Jellyfin.Api/Controllers/SessionController.cs33
-rw-r--r--Jellyfin.Api/Controllers/StudiosController.cs4
-rw-r--r--Jellyfin.Api/Controllers/SubtitleController.cs8
-rw-r--r--Jellyfin.Api/Controllers/SuggestionsController.cs2
-rw-r--r--Jellyfin.Api/Controllers/SyncPlayController.cs54
-rw-r--r--Jellyfin.Api/Controllers/TrailersController.cs22
-rw-r--r--Jellyfin.Api/Controllers/TvShowsController.cs18
-rw-r--r--Jellyfin.Api/Controllers/UniversalAudioController.cs101
-rw-r--r--Jellyfin.Api/Controllers/UserController.cs56
-rw-r--r--Jellyfin.Api/Controllers/UserLibraryController.cs16
-rw-r--r--Jellyfin.Api/Controllers/UserViewsController.cs12
-rw-r--r--Jellyfin.Api/Controllers/VideosController.cs11
-rw-r--r--Jellyfin.Api/Controllers/YearsController.cs4
-rw-r--r--Jellyfin.Api/Extensions/ClaimsPrincipalExtensions.cs81
-rw-r--r--Jellyfin.Api/Extensions/DtoExtensions.cs8
-rw-r--r--Jellyfin.Api/Helpers/AudioHelper.cs8
-rw-r--r--Jellyfin.Api/Helpers/ClaimHelpers.cs88
-rw-r--r--Jellyfin.Api/Helpers/DynamicHlsHelper.cs13
-rw-r--r--Jellyfin.Api/Helpers/MediaInfoHelper.cs48
-rw-r--r--Jellyfin.Api/Helpers/RequestHelpers.cs48
-rw-r--r--Jellyfin.Api/Helpers/StreamingHelpers.cs34
-rw-r--r--Jellyfin.Api/Helpers/TranscodingJobHelper.cs38
-rw-r--r--Jellyfin.Api/Jellyfin.Api.csproj6
-rw-r--r--Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs15
-rw-r--r--Jellyfin.Api/Models/StreamingDtos/StreamState.cs10
-rw-r--r--Jellyfin.Api/Models/SyncPlayDtos/RemoveFromPlaylistRequestDto.cs4
-rw-r--r--Jellyfin.Api/Results/OkResultOfT.cs21
-rw-r--r--Jellyfin.Data/Jellyfin.Data.csproj4
-rw-r--r--Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj7
-rw-r--r--Jellyfin.Drawing.Skia/SkiaEncoder.cs56
-rw-r--r--Jellyfin.Drawing.Skia/SkiaHelper.cs15
-rw-r--r--Jellyfin.Drawing.Skia/StripCollageBuilder.cs5
-rw-r--r--Jellyfin.Networking/Configuration/NetworkConfiguration.cs2
-rw-r--r--Jellyfin.Networking/Jellyfin.Networking.csproj2
-rw-r--r--Jellyfin.Networking/Manager/NetworkManager.cs46
-rw-r--r--Jellyfin.Server.Implementations/Devices/DeviceManager.cs5
-rw-r--r--Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj10
-rw-r--r--Jellyfin.Server.Implementations/Security/AuthorizationContext.cs14
-rw-r--r--Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs2
-rw-r--r--Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs2
-rw-r--r--Jellyfin.Server.Implementations/Users/UserManager.cs18
-rw-r--r--Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs6
-rw-r--r--Jellyfin.Server/Filters/AdditionalModelFilter.cs29
-rw-r--r--Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs11
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj14
-rw-r--r--Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs40
-rw-r--r--Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs18
-rw-r--r--Jellyfin.Server/Program.cs12
-rw-r--r--Jellyfin.Server/Startup.cs17
-rw-r--r--MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs25
-rw-r--r--MediaBrowser.Common/Configuration/EncodingConfigurationExtensions.cs2
-rw-r--r--MediaBrowser.Common/Configuration/IApplicationPaths.cs2
-rw-r--r--MediaBrowser.Common/Configuration/IConfigurationManager.cs6
-rw-r--r--MediaBrowser.Common/Extensions/BaseExtensions.cs6
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj4
-rw-r--r--MediaBrowser.Common/Net/IPNetAddress.cs5
-rw-r--r--MediaBrowser.Common/Net/IPObject.cs54
-rw-r--r--MediaBrowser.Common/Net/NamedClient.cs5
-rw-r--r--MediaBrowser.Common/Net/NetworkExtensions.cs20
-rw-r--r--MediaBrowser.Common/Plugins/BasePluginOfT.cs5
-rw-r--r--MediaBrowser.Controller/Drawing/IImageProcessor.cs8
-rw-r--r--MediaBrowser.Controller/Entities/AggregateFolder.cs5
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs4
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicArtist.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicGenre.cs2
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs39
-rw-r--r--MediaBrowser.Controller/Entities/BaseItemExtensions.cs10
-rw-r--r--MediaBrowser.Controller/Entities/BasePluginFolder.cs4
-rw-r--r--MediaBrowser.Controller/Entities/Extensions.cs8
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs50
-rw-r--r--MediaBrowser.Controller/Entities/Genre.cs2
-rw-r--r--MediaBrowser.Controller/Entities/InternalItemsQuery.cs2
-rw-r--r--MediaBrowser.Controller/Entities/PeopleHelper.cs5
-rw-r--r--MediaBrowser.Controller/Entities/Person.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Studio.cs2
-rw-r--r--MediaBrowser.Controller/Entities/TV/Season.cs2
-rw-r--r--MediaBrowser.Controller/Entities/TV/Series.cs7
-rw-r--r--MediaBrowser.Controller/Entities/UserViewBuilder.cs11
-rw-r--r--MediaBrowser.Controller/IDisplayPreferencesManager.cs6
-rw-r--r--MediaBrowser.Controller/IO/FileData.cs5
-rw-r--r--MediaBrowser.Controller/IServerApplicationHost.cs4
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs8
-rw-r--r--MediaBrowser.Controller/Library/NameExtensions.cs2
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj5
-rw-r--r--MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs6
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs392
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs43
-rw-r--r--MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs13
-rw-r--r--MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs9
-rw-r--r--MediaBrowser.Controller/MediaEncoding/JobLogger.cs2
-rw-r--r--MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs10
-rw-r--r--MediaBrowser.Controller/Net/ISessionContext.cs20
-rw-r--r--MediaBrowser.Controller/Net/IWebSocketConnection.cs3
-rw-r--r--MediaBrowser.Controller/Playlists/Playlist.cs1
-rw-r--r--MediaBrowser.Controller/Providers/ImageRefreshOptions.cs4
-rw-r--r--MediaBrowser.Controller/Resolvers/ResolverPriority.cs5
-rw-r--r--MediaBrowser.Controller/Session/ISessionManager.cs2
-rw-r--r--MediaBrowser.Controller/Session/SessionInfo.cs23
-rw-r--r--MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs2
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs4
-rw-r--r--MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs4
-rw-r--r--MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj2
-rw-r--r--MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs5
-rw-r--r--MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs5
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs44
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs42
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj4
-rw-r--r--MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs5
-rw-r--r--MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs7
-rw-r--r--MediaBrowser.MediaEncoding/Probing/MediaStreamInfoSideData.cs73
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs40
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/AssParser.cs19
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/AssWriter.cs54
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs12
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs19
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs19
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SsaWriter.cs54
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs83
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs178
-rw-r--r--MediaBrowser.Model/Branding/BrandingOptions.cs6
-rw-r--r--MediaBrowser.Model/Configuration/EmbeddedSubtitleOptions.cs2
-rw-r--r--MediaBrowser.Model/Configuration/EncodingOptions.cs10
-rw-r--r--MediaBrowser.Model/Configuration/LibraryOptions.cs2
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs2
-rw-r--r--MediaBrowser.Model/Configuration/UserConfiguration.cs16
-rw-r--r--MediaBrowser.Model/Cryptography/PasswordHash.cs5
-rw-r--r--MediaBrowser.Model/Dlna/ConditionProcessor.cs3
-rw-r--r--MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs6
-rw-r--r--MediaBrowser.Model/Dlna/DeviceProfile.cs4
-rw-r--r--MediaBrowser.Model/Dlna/ProfileConditionValue.cs3
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs162
-rw-r--r--MediaBrowser.Model/Dlna/StreamInfo.cs24
-rw-r--r--MediaBrowser.Model/Dto/BaseItemDto.cs6
-rw-r--r--MediaBrowser.Model/Entities/MediaStream.cs182
-rw-r--r--MediaBrowser.Model/Entities/MediaStreamType.cs7
-rw-r--r--MediaBrowser.Model/Entities/MetadataProvider.cs6
-rw-r--r--MediaBrowser.Model/Entities/ProviderIdsExtensions.cs38
-rw-r--r--MediaBrowser.Model/LiveTv/TunerHostInfo.cs3
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj10
-rw-r--r--MediaBrowser.Model/MediaInfo/SubtitleFormat.cs1
-rw-r--r--MediaBrowser.Model/Querying/ItemSortBy.cs52
-rw-r--r--MediaBrowser.Model/Querying/NextUpQuery.cs2
-rw-r--r--MediaBrowser.Model/Search/SearchHint.cs66
-rw-r--r--MediaBrowser.Model/Session/GeneralCommand.cs4
-rw-r--r--MediaBrowser.Model/Session/PlayerStateInfo.cs6
-rw-r--r--MediaBrowser.Model/Session/TranscodeReason.cs1
-rw-r--r--MediaBrowser.Model/SyncPlay/GroupStateType.cs2
-rw-r--r--MediaBrowser.Model/Tasks/ITaskManager.cs10
-rw-r--r--MediaBrowser.Model/Tasks/ITaskTrigger.cs4
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs20
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs7
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj8
-rw-r--r--MediaBrowser.Providers/MediaInfo/AudioResolver.cs4
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs8
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs39
-rw-r--r--MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs60
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs5
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs11
-rw-r--r--MediaBrowser.Providers/Plugins/StudioImages/Configuration/config.html6
-rw-r--r--MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs1
-rw-r--r--MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs14
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs4
-rw-r--r--MediaBrowser.Providers/TV/SeriesMetadataService.cs57
-rw-r--r--MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj2
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs2
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs2
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs12
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs2
-rw-r--r--README.md11
-rw-r--r--SharedVersion.cs4
-rw-r--r--build.yaml2
-rw-r--r--debian/changelog6
-rw-r--r--debian/conf/jellyfin-sudoers4
-rw-r--r--debian/conf/jellyfin.service.conf48
-rw-r--r--debian/control2
-rw-r--r--debian/jellyfin.service35
-rw-r--r--debian/metapackage/jellyfin2
-rwxr-xr-xdebian/rules2
-rw-r--r--deployment/Dockerfile.centos.amd642
-rw-r--r--deployment/Dockerfile.debian.amd642
-rw-r--r--deployment/Dockerfile.debian.arm642
-rw-r--r--deployment/Dockerfile.debian.armhf2
-rw-r--r--deployment/Dockerfile.docker.amd642
-rw-r--r--deployment/Dockerfile.docker.arm642
-rw-r--r--deployment/Dockerfile.docker.armhf2
-rw-r--r--deployment/Dockerfile.fedora.amd646
-rw-r--r--deployment/Dockerfile.linux.amd642
-rw-r--r--deployment/Dockerfile.linux.amd64-musl2
-rw-r--r--deployment/Dockerfile.linux.arm642
-rw-r--r--deployment/Dockerfile.linux.armhf2
-rw-r--r--deployment/Dockerfile.linux.musl-linux-arm6426
-rw-r--r--deployment/Dockerfile.macos2
-rw-r--r--deployment/Dockerfile.portable2
-rw-r--r--deployment/Dockerfile.ubuntu.amd644
-rw-r--r--deployment/Dockerfile.ubuntu.arm644
-rw-r--r--deployment/Dockerfile.ubuntu.armhf4
-rw-r--r--deployment/Dockerfile.windows.amd642
-rwxr-xr-xdeployment/build.linux.amd642
-rwxr-xr-xdeployment/build.linux.amd64-musl2
-rwxr-xr-xdeployment/build.linux.arm642
-rwxr-xr-xdeployment/build.linux.armhf2
-rwxr-xr-xdeployment/build.linux.musl-linux-arm6431
-rwxr-xr-xdeployment/build.macos2
-rwxr-xr-xdeployment/build.portable2
-rwxr-xr-xdeployment/build.windows.amd642
-rw-r--r--fedora/Makefile11
-rw-r--r--fedora/README.md10
-rw-r--r--fedora/jellyfin.env4
-rw-r--r--fedora/jellyfin.override.conf46
-rw-r--r--fedora/jellyfin.service36
-rw-r--r--fedora/jellyfin.spec97
-rw-r--r--fedora/jellyfin.sudoers4
-rw-r--r--jellyfin.ruleset2
-rw-r--r--src/Jellyfin.Extensions/EnumerableExtensions.cs5
-rw-r--r--src/Jellyfin.Extensions/Jellyfin.Extensions.csproj4
-rw-r--r--src/Jellyfin.Extensions/ReadOnlyListExtension.cs16
-rw-r--r--src/Jellyfin.Extensions/SplitStringExtensions.cs2
-rw-r--r--src/Jellyfin.Extensions/StringExtensions.cs41
-rw-r--r--src/Jellyfin.MediaEncoding.Hls/Extractors/IKeyframeExtractor.cs1
-rw-r--r--src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj2
-rw-r--r--src/Jellyfin.MediaEncoding.Hls/Playlist/CreateMainPlaylistRequest.cs9
-rw-r--r--src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs3
-rw-r--r--src/Jellyfin.MediaEncoding.Keyframes/FfProbe/FfProbeKeyframeExtractor.cs17
-rw-r--r--src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj4
-rw-r--r--tests/Jellyfin.Api.Tests/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandlerTests.cs2
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj15
-rw-r--r--tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs8
-rw-r--r--tests/Jellyfin.Api.Tests/ModelBinders/PipeDelimitedArrayModelBinderTests.cs8
-rw-r--r--tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj13
-rw-r--r--tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj13
-rw-r--r--tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj13
-rw-r--r--tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj8
-rw-r--r--tests/Jellyfin.Extensions.Tests/Json/Converters/JsonStringConverterTests.cs2
-rw-r--r--tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs32
-rw-r--r--tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj8
-rw-r--r--tests/Jellyfin.MediaEncoding.Hls.Tests/Playlist/DynamicHlsPlaylistGeneratorTests.cs4
-rw-r--r--tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj8
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj13
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs8
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs3
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs5
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs7
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Subtitles/SubtitleEncoderTests.cs2
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_metadata.json15
-rw-r--r--tests/Jellyfin.Model.Tests/Cryptography/PasswordHashTests.cs6
-rw-r--r--tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs129
-rw-r--r--tests/Jellyfin.Model.Tests/Drawing/ImageFormatExtensionsTests.cs2
-rw-r--r--tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs46
-rw-r--r--tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj15
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidTVExoPlayer.json216
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json526
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json499
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-dts-srt-2600k.json70
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-truehd-srt-15200k.json74
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-no-streams.json17
-rw-r--r--tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj13
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs3
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs1
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs1
-rw-r--r--tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj15
-rw-r--r--tests/Jellyfin.Networking.Tests/NetworkParseTests.cs62
-rw-r--r--tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj10
-rw-r--r--tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs2
-rw-r--r--tests/Jellyfin.Providers.Tests/Manager/MetadataServiceTests.cs2
-rw-r--r--tests/Jellyfin.Providers.Tests/MediaInfo/AudioResolverTests.cs6
-rw-r--r--tests/Jellyfin.Providers.Tests/MediaInfo/MediaInfoResolverTests.cs16
-rw-r--r--tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs6
-rw-r--r--tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs2
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj13
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/MediaStreamSelectorTests.cs36
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs11
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs2
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/QuickConnect/QuickConnectManagerTests.cs25
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Sorting/IndexNumberComparerTests.cs49
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Sorting/ParentIndexNumberComparerTests.cs49
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/manifest-stable.json2
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs1
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs54
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs8
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs2
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj15
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs1
-rw-r--r--tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj15
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj13
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs1
470 files changed, 6137 insertions, 9067 deletions
diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml
index 19d65ea0c..926d1d322 100644
--- a/.ci/azure-pipelines-package.yml
+++ b/.ci/azure-pipelines-package.yml
@@ -26,6 +26,8 @@ jobs:
BuildConfiguration: linux.amd64-musl
Linux.arm64:
BuildConfiguration: linux.arm64
+ Linux.musl-linux-arm64:
+ BuildConfiguration: linux.musl-linux-arm64
Linux.armhf:
BuildConfiguration: linux.armhf
Windows.amd64:
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index a648093ed..1dbd7fa36 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -22,16 +22,16 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup .NET Core
- uses: actions/setup-dotnet@v2
+ uses: actions/setup-dotnet@v3
with:
dotnet-version: '6.0.x'
- name: Initialize CodeQL
- uses: github/codeql-action/init@v1
+ uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
- uses: github/codeql-action/autobuild@v1
+ uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
+ uses: github/codeql-action/analyze@v2
diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml
index 26cd24735..23873706d 100644
--- a/.github/workflows/commands.yml
+++ b/.github/workflows/commands.yml
@@ -29,7 +29,7 @@ jobs:
fetch-depth: 0
- name: Automatic Rebase
- uses: cirrus-actions/rebase@1.5
+ uses: cirrus-actions/rebase@1.7
env:
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml
index b8fe561b9..ceb4e8cdf 100644
--- a/.github/workflows/openapi.yml
+++ b/.github/workflows/openapi.yml
@@ -17,13 +17,13 @@ jobs:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- name: Setup .NET Core
- uses: actions/setup-dotnet@v2
+ uses: actions/setup-dotnet@v3
with:
dotnet-version: '6.0.x'
- name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: openapi-head
retention-days: 14
@@ -41,13 +41,13 @@ jobs:
with:
ref: ${{ github.base_ref }}
- name: Setup .NET Core
- uses: actions/setup-dotnet@v2
+ uses: actions/setup-dotnet@v3
with:
dotnet-version: '6.0.x'
- name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: openapi-base
retention-days: 14
@@ -63,12 +63,12 @@ jobs:
- openapi-base
steps:
- name: Download openapi-head
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v3
with:
name: openapi-head
path: openapi-head
- name: Download openapi-base
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v3
with:
name: openapi-base
path: openapi-base
diff --git a/.github/workflows/repo-stale.yaml b/.github/workflows/repo-stale.yaml
index 0504b1c50..2578f82cf 100644
--- a/.github/workflows/repo-stale.yaml
+++ b/.github/workflows/repo-stale.yaml
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ contains(github.repository, 'jellyfin/') }}
steps:
- - uses: actions/stale@v5
+ - uses: actions/stale@v6
with:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
days-before-stale: 120
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 87086a728..8daaae4d9 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -36,6 +36,7 @@
- [dmitrylyzo](https://github.com/dmitrylyzo)
- [DMouse10462](https://github.com/DMouse10462)
- [DrPandemic](https://github.com/DrPandemic)
+ - [eglia](https://github.com/eglia)
- [EraYaN](https://github.com/EraYaN)
- [escabe](https://github.com/escabe)
- [excelite](https://github.com/excelite)
@@ -147,6 +148,7 @@
- [xosdy](https://github.com/xosdy)
- [XVicarious](https://github.com/XVicarious)
- [YouKnowBlom](https://github.com/YouKnowBlom)
+ - [ZachPhelan](https://github.com/ZachPhelan)
- [KristupasSavickas](https://github.com/KristupasSavickas)
- [Pusta](https://github.com/pusta)
- [nielsvanvelzen](https://github.com/nielsvanvelzen)
@@ -157,6 +159,7 @@
- [jonas-resch](https://github.com/jonas-resch)
- [vgambier](https://github.com/vgambier)
- [MinecraftPlaye](https://github.com/MinecraftPlaye)
+ - [RealGreenDragon](https://github.com/RealGreenDragon)
# Emby Contributors
@@ -225,3 +228,4 @@
- [gnuyent](https://github.com/gnuyent)
- [Matthew Jones](https://github.com/matthew-jones-uk)
- [Jakob Kukla](https://github.com/jakobkukla)
+ - [Utku Özdemir](https://github.com/utkuozdemir)
diff --git a/Dockerfile b/Dockerfile
index c3038b1d2..219b95893 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -31,7 +31,7 @@ ARG LEVEL_ZERO_VERSION=1.3.22549
# mesa-va-drivers: needed for AMD VAAPI. Mesa >= 20.1 is required for HEVC transcoding.
# curl: healthcheck
RUN apt-get update \
- && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget apt-transport-https curl \
+ && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget curl \
&& wget -O - https://repo.jellyfin.org/jellyfin_team.gpg.key | apt-key add - \
&& echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \
&& apt-get update \
@@ -53,7 +53,7 @@ RUN apt-get update \
&& dpkg -i *.deb \
&& cd .. \
&& rm -rf intel-compute-runtime \
- && apt-get remove gnupg wget apt-transport-https -y \
+ && apt-get remove gnupg wget -y \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \
@@ -72,7 +72,7 @@ COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# because of changes in docker and systemd we need to not build in parallel at the moment
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
-RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 -p:DebugSymbols=false -p:DebugType=none
FROM app
diff --git a/Dockerfile.arm b/Dockerfile.arm
index b25e6039f..8e0ba7af5 100644
--- a/Dockerfile.arm
+++ b/Dockerfile.arm
@@ -38,9 +38,6 @@ RUN apt-get update \
libssl-dev \
libfontconfig1 \
libfreetype6 \
- libomxil-bellagio0 \
- libomxil-bellagio-bin \
- libraspberrypi0 \
vainfo \
libva2 \
locales \
@@ -64,7 +61,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r
# Build
-RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm -p:DebugSymbols=false -p:DebugType=none
FROM app
diff --git a/Dockerfile.arm64 b/Dockerfile.arm64
index d0be834dd..790be1c39 100644
--- a/Dockerfile.arm64
+++ b/Dockerfile.arm64
@@ -55,7 +55,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r
# Build
-RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 -p:DebugSymbols=false -p:DebugType=none
FROM app
diff --git a/Emby.Dlna/Configuration/DlnaOptions.cs b/Emby.Dlna/Configuration/DlnaOptions.cs
index 91fac4bef..e95a878c6 100644
--- a/Emby.Dlna/Configuration/DlnaOptions.cs
+++ b/Emby.Dlna/Configuration/DlnaOptions.cs
@@ -13,7 +13,7 @@ namespace Emby.Dlna.Configuration
public DlnaOptions()
{
EnablePlayTo = true;
- EnableServer = true;
+ EnableServer = false;
BlastAliveMessages = true;
SendOnlyMatchedHost = true;
ClientDiscoveryIntervalSeconds = 60;
diff --git a/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs b/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs
index 9020dea99..319a9f550 100644
--- a/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs
+++ b/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs
@@ -103,10 +103,7 @@ namespace Emby.Dlna.ContentDirectory
/// <inheritdoc />
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
{
- if (request == null)
- {
- throw new ArgumentNullException(nameof(request));
- }
+ ArgumentNullException.ThrowIfNull(request);
var profile = _dlna.GetProfile(request.Headers) ?? _dlna.GetDefaultProfile();
diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs
index 57170bb31..fc69960fd 100644
--- a/Emby.Dlna/ContentDirectory/ControlHandler.cs
+++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs
@@ -114,15 +114,9 @@ namespace Emby.Dlna.ContentDirectory
/// <inheritdoc />
protected override void WriteResult(string methodName, IReadOnlyDictionary<string, string> methodParams, XmlWriter xmlWriter)
{
- if (xmlWriter == null)
- {
- throw new ArgumentNullException(nameof(xmlWriter));
- }
+ ArgumentNullException.ThrowIfNull(xmlWriter);
- if (methodParams == null)
- {
- throw new ArgumentNullException(nameof(methodParams));
- }
+ ArgumentNullException.ThrowIfNull(methodParams);
const string DeviceId = "test";
diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs
index 6ab5843c1..8e3a335c6 100644
--- a/Emby.Dlna/Didl/DidlBuilder.cs
+++ b/Emby.Dlna/Didl/DidlBuilder.cs
@@ -221,6 +221,7 @@ namespace Emby.Dlna.Didl
streamInfo.IsDirectStream,
streamInfo.RunTimeTicks ?? 0,
streamInfo.TargetVideoProfile,
+ streamInfo.TargetVideoRangeType,
streamInfo.TargetVideoLevel,
streamInfo.TargetFramerate ?? 0,
streamInfo.TargetPacketLength,
@@ -376,6 +377,7 @@ namespace Emby.Dlna.Didl
targetHeight,
streamInfo.TargetVideoBitDepth,
streamInfo.TargetVideoProfile,
+ streamInfo.TargetVideoRangeType,
streamInfo.TargetVideoLevel,
streamInfo.TargetFramerate ?? 0,
streamInfo.TargetPacketLength,
@@ -444,7 +446,7 @@ namespace Emby.Dlna.Didl
/// </summary>
/// <remarks>
/// If context is a season, this will return a string containing just episode number and name.
- /// Otherwise the result will include series nams and season number.
+ /// Otherwise the result will include series names and season number.
/// </remarks>
/// <param name="episode">The episode.</param>
/// <param name="context">Current context.</param>
diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs
index fe78d74ee..2ea2c130f 100644
--- a/Emby.Dlna/DlnaManager.cs
+++ b/Emby.Dlna/DlnaManager.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -61,6 +62,7 @@ namespace Emby.Dlna
try
{
await ExtractSystemProfilesAsync().ConfigureAwait(false);
+ Directory.CreateDirectory(UserProfilesPath);
LoadProfiles();
}
catch (Exception ex)
@@ -100,10 +102,7 @@ namespace Emby.Dlna
/// <inheritdoc />
public DeviceProfile? GetProfile(DeviceIdentification deviceInfo)
{
- if (deviceInfo == null)
- {
- throw new ArgumentNullException(nameof(deviceInfo));
- }
+ ArgumentNullException.ThrowIfNull(deviceInfo);
var profile = GetProfiles()
.FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification));
@@ -123,7 +122,7 @@ namespace Emby.Dlna
/// <summary>
/// Attempts to match a device with a profile.
/// Rules:
- /// - If the profile field has no value, the field matches irregardless of its contents.
+ /// - If the profile field has no value, the field matches regardless of its contents.
/// - the profile field can be an exact match, or a reg exp.
/// </summary>
/// <param name="deviceInfo">The <see cref="DeviceIdentification"/> of the device.</param>
@@ -170,10 +169,7 @@ namespace Emby.Dlna
/// <inheritdoc />
public DeviceProfile? GetProfile(IHeaderDictionary headers)
{
- if (headers == null)
- {
- throw new ArgumentNullException(nameof(headers));
- }
+ ArgumentNullException.ThrowIfNull(headers);
var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
if (profile == null)
@@ -328,32 +324,28 @@ namespace Emby.Dlna
var path = Path.Join(
systemProfilesPath,
- Path.GetFileName(name.AsSpan()).Slice(namespaceName.Length));
+ Path.GetFileName(name.AsSpan())[namespaceName.Length..]);
+
+ if (File.Exists(path))
+ {
+ continue;
+ }
// The stream should exist as we just got its name from GetManifestResourceNames
using (var stream = _assembly.GetManifestResourceStream(name)!)
{
- var length = stream.Length;
- var fileInfo = _fileSystem.GetFileInfo(path);
+ Directory.CreateDirectory(systemProfilesPath);
- if (!fileInfo.Exists || fileInfo.Length != length)
+ var fileOptions = AsyncFile.WriteOptions;
+ fileOptions.Mode = FileMode.CreateNew;
+ fileOptions.PreallocationSize = stream.Length;
+ var fileStream = new FileStream(path, fileOptions);
+ await using (fileStream.ConfigureAwait(false))
{
- Directory.CreateDirectory(systemProfilesPath);
-
- var fileOptions = AsyncFile.WriteOptions;
- fileOptions.Mode = FileMode.Create;
- fileOptions.PreallocationSize = length;
- var fileStream = new FileStream(path, fileOptions);
- await using (fileStream.ConfigureAwait(false))
- {
- await stream.CopyToAsync(fileStream).ConfigureAwait(false);
- }
+ await stream.CopyToAsync(fileStream).ConfigureAwait(false);
}
}
}
-
- // Not necessary, but just to make it easy to find
- Directory.CreateDirectory(UserProfilesPath);
}
/// <inheritdoc />
@@ -406,14 +398,20 @@ namespace Emby.Dlna
}
var current = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, profileId, StringComparison.OrdinalIgnoreCase));
+ if (current.Info.Type == DeviceProfileType.System)
+ {
+ throw new ArgumentException("System profiles can't be edited");
+ }
var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml";
- var path = Path.Combine(UserProfilesPath, newFilename);
+ var path = Path.Join(UserProfilesPath, newFilename);
- if (!string.Equals(path, current.Path, StringComparison.Ordinal) &&
- current.Info.Type != DeviceProfileType.System)
+ if (!string.Equals(path, current.Path, StringComparison.Ordinal))
{
- _fileSystem.DeleteFile(current.Path);
+ lock (_profiles)
+ {
+ _profiles.Remove(current.Path);
+ }
}
SaveProfile(profile, path, DeviceProfileType.User);
@@ -496,72 +494,4 @@ namespace Emby.Dlna
internal string Path { get; }
}
}
-
- /*
- class DlnaProfileEntryPoint : IServerEntryPoint
- {
- private readonly IApplicationPaths _appPaths;
- private readonly IFileSystem _fileSystem;
- private readonly IXmlSerializer _xmlSerializer;
-
- public DlnaProfileEntryPoint(IApplicationPaths appPaths, IFileSystem fileSystem, IXmlSerializer xmlSerializer)
- {
- _appPaths = appPaths;
- _fileSystem = fileSystem;
- _xmlSerializer = xmlSerializer;
- }
-
- public void Run()
- {
- DumpProfiles();
- }
-
- private void DumpProfiles()
- {
- DeviceProfile[] list = new[]
- {
- new SamsungSmartTvProfile(),
- new XboxOneProfile(),
- new SonyPs3Profile(),
- new SonyPs4Profile(),
- new SonyBravia2010Profile(),
- new SonyBravia2011Profile(),
- new SonyBravia2012Profile(),
- new SonyBravia2013Profile(),
- new SonyBravia2014Profile(),
- new SonyBlurayPlayer2013(),
- new SonyBlurayPlayer2014(),
- new SonyBlurayPlayer2015(),
- new SonyBlurayPlayer2016(),
- new SonyBlurayPlayerProfile(),
- new PanasonicVieraProfile(),
- new WdtvLiveProfile(),
- new DenonAvrProfile(),
- new LinksysDMA2100Profile(),
- new LgTvProfile(),
- new Foobar2000Profile(),
- new SharpSmartTvProfile(),
- new MediaMonkeyProfile(),
- // new Windows81Profile(),
- // new WindowsMediaCenterProfile(),
- // new WindowsPhoneProfile(),
- new DirectTvProfile(),
- new DishHopperJoeyProfile(),
- new DefaultProfile(),
- new PopcornHourProfile(),
- new MarantzProfile()
- };
-
- foreach (var item in list)
- {
- var path = Path.Combine(_appPaths.ProgramDataPath, _fileSystem.GetValidFilename(item.Name) + ".xml");
-
- _xmlSerializer.SerializeToFile(item, path);
- }
- }
-
- public void Dispose()
- {
- }
- }*/
}
diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj
index 472fe140a..d59b43ef0 100644
--- a/Emby.Dlna/Emby.Dlna.csproj
+++ b/Emby.Dlna/Emby.Dlna.csproj
@@ -33,7 +33,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/Emby.Dlna/IDlnaEventManager.cs b/Emby.Dlna/IDlnaEventManager.cs
index 33cf0896b..eea030d6d 100644
--- a/Emby.Dlna/IDlnaEventManager.cs
+++ b/Emby.Dlna/IDlnaEventManager.cs
@@ -16,7 +16,7 @@ namespace Emby.Dlna
/// </summary>
/// <param name="subscriptionId">The subscription identifier.</param>
/// <param name="notificationType">The notification type.</param>
- /// <param name="requestedTimeoutString">The requested timeout as a sting.</param>
+ /// <param name="requestedTimeoutString">The requested timeout as a string.</param>
/// <param name="callbackUrl">The callback url.</param>
/// <returns>The response.</returns>
EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl);
@@ -25,7 +25,7 @@ namespace Emby.Dlna
/// Creates the event subscription.
/// </summary>
/// <param name="notificationType">The notification type.</param>
- /// <param name="requestedTimeoutString">The requested timeout as a sting.</param>
+ /// <param name="requestedTimeoutString">The requested timeout as a string.</param>
/// <param name="callbackUrl">The callback url.</param>
/// <returns>The response.</returns>
EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl);
diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs
index 2a535d556..15021c19d 100644
--- a/Emby.Dlna/Main/DlnaEntryPoint.cs
+++ b/Emby.Dlna/Main/DlnaEntryPoint.cs
@@ -313,7 +313,7 @@ namespace Emby.Dlna.Main
_logger.LogInformation("Registering publisher for {ResourceName} on {DeviceAddress}", fullService, address);
- var uri = new UriBuilder(_appHost.GetApiUrlForLocalAccess(false) + descriptorUri);
+ var uri = new UriBuilder(_appHost.GetApiUrlForLocalAccess(address, false) + descriptorUri);
var device = new SsdpRootDevice
{
diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs
index 8eb90f445..34981bd3f 100644
--- a/Emby.Dlna/PlayTo/Device.cs
+++ b/Emby.Dlna/PlayTo/Device.cs
@@ -235,7 +235,7 @@ namespace Emby.Dlna.PlayTo
_logger.LogDebug("Setting mute");
var value = mute ? 1 : 0;
- await new SsdpHttpClient(_httpClientFactory)
+ await new DlnaHttpClient(_logger, _httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
service,
@@ -276,7 +276,7 @@ namespace Emby.Dlna.PlayTo
// Remote control will perform better
Volume = value;
- await new SsdpHttpClient(_httpClientFactory)
+ await new DlnaHttpClient(_logger, _httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
service,
@@ -303,7 +303,7 @@ namespace Emby.Dlna.PlayTo
throw new InvalidOperationException("Unable to find service");
}
- await new SsdpHttpClient(_httpClientFactory)
+ await new DlnaHttpClient(_logger, _httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
service,
@@ -343,7 +343,7 @@ namespace Emby.Dlna.PlayTo
}
var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary);
- await new SsdpHttpClient(_httpClientFactory)
+ await new DlnaHttpClient(_logger, _httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
service,
@@ -400,7 +400,8 @@ namespace Emby.Dlna.PlayTo
}
var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary);
- await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header, cancellationToken)
+ await new DlnaHttpClient(_logger, _httpClientFactory)
+ .SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header, cancellationToken)
.ConfigureAwait(false);
}
@@ -428,7 +429,7 @@ namespace Emby.Dlna.PlayTo
throw new InvalidOperationException("Unable to find service");
}
- return new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
+ return new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
@@ -461,7 +462,7 @@ namespace Emby.Dlna.PlayTo
var service = GetAvTransportService();
- await new SsdpHttpClient(_httpClientFactory)
+ await new DlnaHttpClient(_logger, _httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
service,
@@ -485,7 +486,7 @@ namespace Emby.Dlna.PlayTo
var service = GetAvTransportService();
- await new SsdpHttpClient(_httpClientFactory)
+ await new DlnaHttpClient(_logger, _httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
service,
@@ -618,7 +619,7 @@ namespace Emby.Dlna.PlayTo
return;
}
- var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
+ var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
@@ -668,7 +669,7 @@ namespace Emby.Dlna.PlayTo
return;
}
- var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
+ var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
@@ -701,7 +702,7 @@ namespace Emby.Dlna.PlayTo
return null;
}
- var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
+ var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
@@ -747,7 +748,7 @@ namespace Emby.Dlna.PlayTo
return null;
}
- var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
+ var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
@@ -819,7 +820,7 @@ namespace Emby.Dlna.PlayTo
return (false, null);
}
- var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
+ var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
@@ -930,10 +931,7 @@ namespace Emby.Dlna.PlayTo
private static UBaseObject CreateUBaseObject(XElement container, string trackUri)
{
- if (container == null)
- {
- throw new ArgumentNullException(nameof(container));
- }
+ ArgumentNullException.ThrowIfNull(container);
var url = container.GetValue(UPnpNamespaces.Res);
@@ -957,10 +955,7 @@ namespace Emby.Dlna.PlayTo
private static string[] GetProtocolInfo(XElement container)
{
- if (container == null)
- {
- throw new ArgumentNullException(nameof(container));
- }
+ ArgumentNullException.ThrowIfNull(container);
var resElement = container.Element(UPnpNamespaces.Res);
@@ -997,7 +992,7 @@ namespace Emby.Dlna.PlayTo
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
- var httpClient = new SsdpHttpClient(_httpClientFactory);
+ var httpClient = new DlnaHttpClient(_logger, _httpClientFactory);
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
if (document == null)
@@ -1029,7 +1024,7 @@ namespace Emby.Dlna.PlayTo
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
- var httpClient = new SsdpHttpClient(_httpClientFactory);
+ var httpClient = new DlnaHttpClient(_logger, _httpClientFactory);
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
if (document == null)
@@ -1064,7 +1059,7 @@ namespace Emby.Dlna.PlayTo
public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClientFactory httpClientFactory, ILogger logger, CancellationToken cancellationToken)
{
- var ssdpHttpClient = new SsdpHttpClient(httpClientFactory);
+ var ssdpHttpClient = new DlnaHttpClient(logger, httpClientFactory);
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
if (document == null)
@@ -1182,10 +1177,7 @@ namespace Emby.Dlna.PlayTo
#nullable enable
private static DeviceIcon CreateIcon(XElement element)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
+ ArgumentNullException.ThrowIfNull(element);
var width = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("width"));
var height = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("height"));
diff --git a/Emby.Dlna/PlayTo/DlnaHttpClient.cs b/Emby.Dlna/PlayTo/DlnaHttpClient.cs
new file mode 100644
index 000000000..75ff542dd
--- /dev/null
+++ b/Emby.Dlna/PlayTo/DlnaHttpClient.cs
@@ -0,0 +1,108 @@
+#pragma warning disable CS1591
+
+using System;
+using System.Globalization;
+using System.Net.Http;
+using System.Net.Mime;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Linq;
+using Emby.Dlna.Common;
+using MediaBrowser.Common.Net;
+using Microsoft.Extensions.Logging;
+
+namespace Emby.Dlna.PlayTo
+{
+ public class DlnaHttpClient
+ {
+ private readonly ILogger _logger;
+ private readonly IHttpClientFactory _httpClientFactory;
+
+ public DlnaHttpClient(ILogger logger, IHttpClientFactory httpClientFactory)
+ {
+ _logger = logger;
+ _httpClientFactory = httpClientFactory;
+ }
+
+ private static string NormalizeServiceUrl(string baseUrl, string serviceUrl)
+ {
+ // If it's already a complete url, don't stick anything onto the front of it
+ if (serviceUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+ {
+ return serviceUrl;
+ }
+
+ if (!serviceUrl.StartsWith('/'))
+ {
+ serviceUrl = "/" + serviceUrl;
+ }
+
+ return baseUrl + serviceUrl;
+ }
+
+ private async Task<XDocument?> SendRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Dlna).SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ response.EnsureSuccessStatusCode();
+ await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+ try
+ {
+ return await XDocument.LoadAsync(
+ stream,
+ LoadOptions.None,
+ cancellationToken).ConfigureAwait(false);
+ }
+ catch (XmlException ex)
+ {
+ _logger.LogError(ex, "Failed to parse response");
+ if (_logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("Malformed response: {Content}\n", await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false));
+ }
+
+ return null;
+ }
+ }
+
+ public async Task<XDocument?> GetDataAsync(string url, CancellationToken cancellationToken)
+ {
+ using var request = new HttpRequestMessage(HttpMethod.Get, url);
+
+ // Have to await here instead of returning the Task directly, otherwise request would be disposed too soon
+ return await SendRequestAsync(request, cancellationToken).ConfigureAwait(false);
+ }
+
+ public async Task<XDocument?> SendCommandAsync(
+ string baseUrl,
+ DeviceService service,
+ string command,
+ string postData,
+ string? header = null,
+ CancellationToken cancellationToken = default)
+ {
+ using var request = new HttpRequestMessage(HttpMethod.Post, NormalizeServiceUrl(baseUrl, service.ControlUrl))
+ {
+ Content = new StringContent(postData, Encoding.UTF8, MediaTypeNames.Text.Xml)
+ };
+
+ request.Headers.TryAddWithoutValidation(
+ "SOAPACTION",
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "\"{0}#{1}\"",
+ service.ServiceType,
+ command));
+ request.Headers.Pragma.ParseAdd("no-cache");
+
+ if (!string.IsNullOrEmpty(header))
+ {
+ request.Headers.TryAddWithoutValidation("contentFeatures.dlna.org", header);
+ }
+
+ // Have to await here instead of returning the Task directly, otherwise request would be disposed too soon
+ return await SendRequestAsync(request, cancellationToken).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs
index e27a8975b..b73ce00b6 100644
--- a/Emby.Dlna/PlayTo/PlayToController.cs
+++ b/Emby.Dlna/PlayTo/PlayToController.cs
@@ -561,6 +561,7 @@ namespace Emby.Dlna.PlayTo
streamInfo.IsDirectStream,
streamInfo.RunTimeTicks ?? 0,
streamInfo.TargetVideoProfile,
+ streamInfo.TargetVideoRangeType,
streamInfo.TargetVideoLevel,
streamInfo.TargetFramerate ?? 0,
streamInfo.TargetPacketLength,
diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
deleted file mode 100644
index cade7b4c2..000000000
--- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs
+++ /dev/null
@@ -1,141 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System;
-using System.Globalization;
-using System.Net.Http;
-using System.Net.Mime;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Xml.Linq;
-using Emby.Dlna.Common;
-using MediaBrowser.Common.Net;
-
-namespace Emby.Dlna.PlayTo
-{
- public class SsdpHttpClient
- {
- private const string USERAGENT = "Microsoft-Windows/6.2 UPnP/1.0 Microsoft-DLNA DLNADOC/1.50";
- private const string FriendlyName = "Jellyfin";
-
- private readonly IHttpClientFactory _httpClientFactory;
-
- public SsdpHttpClient(IHttpClientFactory httpClientFactory)
- {
- _httpClientFactory = httpClientFactory;
- }
-
- public async Task<XDocument> SendCommandAsync(
- string baseUrl,
- DeviceService service,
- string command,
- string postData,
- string header = null,
- CancellationToken cancellationToken = default)
- {
- var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
- using var response = await PostSoapDataAsync(
- url,
- $"\"{service.ServiceType}#{command}\"",
- postData,
- header,
- cancellationToken)
- .ConfigureAwait(false);
- response.EnsureSuccessStatusCode();
-
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- return await XDocument.LoadAsync(
- stream,
- LoadOptions.None,
- cancellationToken).ConfigureAwait(false);
- }
-
- private static string NormalizeServiceUrl(string baseUrl, string serviceUrl)
- {
- // If it's already a complete url, don't stick anything onto the front of it
- if (serviceUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
- {
- return serviceUrl;
- }
-
- if (!serviceUrl.StartsWith('/'))
- {
- serviceUrl = "/" + serviceUrl;
- }
-
- return baseUrl + serviceUrl;
- }
-
- public async Task SubscribeAsync(
- string url,
- string ip,
- int port,
- string localIp,
- int eventport,
- int timeOut = 3600)
- {
- using var options = new HttpRequestMessage(new HttpMethod("SUBSCRIBE"), url);
- options.Headers.UserAgent.ParseAdd(USERAGENT);
- options.Headers.TryAddWithoutValidation("HOST", ip + ":" + port.ToString(CultureInfo.InvariantCulture));
- options.Headers.TryAddWithoutValidation("CALLBACK", "<" + localIp + ":" + eventport.ToString(CultureInfo.InvariantCulture) + ">");
- options.Headers.TryAddWithoutValidation("NT", "upnp:event");
- options.Headers.TryAddWithoutValidation("TIMEOUT", "Second-" + timeOut.ToString(CultureInfo.InvariantCulture));
-
- using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
- .SendAsync(options, HttpCompletionOption.ResponseHeadersRead)
- .ConfigureAwait(false);
- response.EnsureSuccessStatusCode();
- }
-
- public async Task<XDocument> GetDataAsync(string url, CancellationToken cancellationToken)
- {
- using var options = new HttpRequestMessage(HttpMethod.Get, url);
- options.Headers.UserAgent.ParseAdd(USERAGENT);
- options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
- using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- response.EnsureSuccessStatusCode();
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- try
- {
- return await XDocument.LoadAsync(
- stream,
- LoadOptions.None,
- cancellationToken).ConfigureAwait(false);
- }
- catch
- {
- return null;
- }
- }
-
- private async Task<HttpResponseMessage> PostSoapDataAsync(
- string url,
- string soapAction,
- string postData,
- string header,
- CancellationToken cancellationToken)
- {
- if (soapAction[0] != '\"')
- {
- soapAction = $"\"{soapAction}\"";
- }
-
- using var options = new HttpRequestMessage(HttpMethod.Post, url);
- options.Headers.UserAgent.ParseAdd(USERAGENT);
- options.Headers.TryAddWithoutValidation("SOAPACTION", soapAction);
- options.Headers.TryAddWithoutValidation("Pragma", "no-cache");
- options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
-
- if (!string.IsNullOrEmpty(header))
- {
- options.Headers.TryAddWithoutValidation("contentFeatures.dlna.org", header);
- }
-
- options.Content = new StringContent(postData, Encoding.UTF8, MediaTypeNames.Text.Xml);
-
- return await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- }
- }
-}
diff --git a/Emby.Dlna/PlayTo/TransportCommands.cs b/Emby.Dlna/PlayTo/TransportCommands.cs
index d373b57f5..9c3a9103b 100644
--- a/Emby.Dlna/PlayTo/TransportCommands.cs
+++ b/Emby.Dlna/PlayTo/TransportCommands.cs
@@ -61,10 +61,7 @@ namespace Emby.Dlna.PlayTo
private static Argument ArgumentFromXml(XElement container)
{
- if (container == null)
- {
- throw new ArgumentNullException(nameof(container));
- }
+ ArgumentNullException.ThrowIfNull(container);
return new Argument
{
diff --git a/Emby.Dlna/PlayTo/UpnpContainer.cs b/Emby.Dlna/PlayTo/UpnpContainer.cs
index 05f27603f..017d51e60 100644
--- a/Emby.Dlna/PlayTo/UpnpContainer.cs
+++ b/Emby.Dlna/PlayTo/UpnpContainer.cs
@@ -10,10 +10,7 @@ namespace Emby.Dlna.PlayTo
{
public static UBaseObject Create(XElement container)
{
- if (container == null)
- {
- throw new ArgumentNullException(nameof(container));
- }
+ ArgumentNullException.ThrowIfNull(container);
return new UBaseObject
{
diff --git a/Emby.Dlna/PlayTo/uBaseObject.cs b/Emby.Dlna/PlayTo/uBaseObject.cs
index 02d2da58d..2e0f2063b 100644
--- a/Emby.Dlna/PlayTo/uBaseObject.cs
+++ b/Emby.Dlna/PlayTo/uBaseObject.cs
@@ -54,10 +54,7 @@ namespace Emby.Dlna.PlayTo
public bool Equals(UBaseObject obj)
{
- if (obj == null)
- {
- throw new ArgumentNullException(nameof(obj));
- }
+ ArgumentNullException.ThrowIfNull(obj);
return string.Equals(Id, obj.Id, StringComparison.Ordinal);
}
diff --git a/Emby.Dlna/Profiles/DefaultProfile.cs b/Emby.Dlna/Profiles/DefaultProfile.cs
index 8f4f2bd38..23437f1bd 100644
--- a/Emby.Dlna/Profiles/DefaultProfile.cs
+++ b/Emby.Dlna/Profiles/DefaultProfile.cs
@@ -2,7 +2,6 @@
using System;
using System.Globalization;
-using System.Linq;
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
@@ -164,18 +163,5 @@ namespace Emby.Dlna.Profiles
}
};
}
-
- public void AddXmlRootAttribute(string name, string value)
- {
- var list = XmlRootAttributes.ToList();
-
- list.Add(new XmlAttribute
- {
- Name = name,
- Value = value
- });
-
- XmlRootAttributes = list.ToArray();
- }
}
}
diff --git a/Emby.Dlna/Profiles/DenonAvrProfile.cs b/Emby.Dlna/Profiles/DenonAvrProfile.cs
deleted file mode 100644
index a5ba0f36c..000000000
--- a/Emby.Dlna/Profiles/DenonAvrProfile.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class DenonAvrProfile : DefaultProfile
- {
- public DenonAvrProfile()
- {
- Name = "Denon AVR";
-
- SupportedMediaTypes = "Audio";
-
- Identification = new DeviceIdentification
- {
- FriendlyName = @"Denon:\[AVR:.*",
- Manufacturer = "Denon"
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "mp3,flac,m4a,wma",
- Type = DlnaProfileType.Audio
- },
- };
-
- ResponseProfiles = System.Array.Empty<ResponseProfile>();
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/DirectTvProfile.cs b/Emby.Dlna/Profiles/DirectTvProfile.cs
deleted file mode 100644
index f6f98b07d..000000000
--- a/Emby.Dlna/Profiles/DirectTvProfile.cs
+++ /dev/null
@@ -1,129 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class DirectTvProfile : DefaultProfile
- {
- public DirectTvProfile()
- {
- Name = "DirecTV HD-DVR";
-
- TimelineOffsetSeconds = 10;
- RequiresPlainFolders = true;
- RequiresPlainVideoItems = true;
-
- Identification = new DeviceIdentification
- {
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Match = HeaderMatchType.Substring,
- Name = "User-Agent",
- Value = "DIRECTV"
- }
- },
-
- FriendlyName = "^DIRECTV.*$"
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "mpeg",
- VideoCodec = "mpeg2video",
- AudioCodec = "mp2",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "jpeg,jpg",
- Type = DlnaProfileType.Photo
- }
- };
-
- TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Container = "mpeg",
- VideoCodec = "mpeg2video",
- AudioCodec = "mp2",
- Type = DlnaProfileType.Video
- },
- new TranscodingProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- CodecProfiles = new[]
- {
- new CodecProfile
- {
- Codec = "mpeg2video",
- Type = CodecType.Video,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitrate,
- Value = "8192000"
- }
- }
- },
- new CodecProfile
- {
- Codec = "mp2",
- Type = CodecType.Audio,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "2"
- }
- }
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- }
- };
-
- ResponseProfiles = System.Array.Empty<ResponseProfile>();
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/DishHopperJoeyProfile.cs b/Emby.Dlna/Profiles/DishHopperJoeyProfile.cs
deleted file mode 100644
index 2a7524a6a..000000000
--- a/Emby.Dlna/Profiles/DishHopperJoeyProfile.cs
+++ /dev/null
@@ -1,228 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class DishHopperJoeyProfile : DefaultProfile
- {
- public DishHopperJoeyProfile()
- {
- Name = "Dish Hopper-Joey";
-
- ProtocolInfo = "http-get:*:video/mp2t:*,http-get:*:video/mpeg:*,http-get:*:video/MP1S:*,http-get:*:video/mpeg2:*,http-get:*:video/mp4:*,http-get:*:video/x-matroska:*,http-get:*:audio/mpeg:*,http-get:*:audio/mpeg3:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/mp4a-latm:*,http-get:*:image/jpeg:*";
-
- Identification = new DeviceIdentification
- {
- Manufacturer = "Echostar Technologies LLC",
- ManufacturerUrl = "http://www.echostar.com",
-
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Match = HeaderMatchType.Substring,
- Name = "User-Agent",
- Value = "Zip_"
- }
- }
- };
-
- TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
- new TranscodingProfile
- {
- Container = "mp4",
- Type = DlnaProfileType.Video,
- AudioCodec = "aac",
- VideoCodec = "h264"
- },
-
- new TranscodingProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "mp4,mkv,mpeg,ts",
- VideoCodec = "h264,mpeg2video",
- AudioCodec = "mp3,ac3,aac,he-aac,pcm",
- Type = DlnaProfileType.Video
- },
-
- new DirectPlayProfile
- {
- Container = "mp3,alac,flac",
- Type = DlnaProfileType.Audio
- },
-
- new DirectPlayProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- CodecProfiles = new[]
- {
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "h264",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920",
- IsRequired = true
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080",
- IsRequired = true
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30",
- IsRequired = true
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitrate,
- Value = "20000000",
- IsRequired = true
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoLevel,
- Value = "41",
- IsRequired = true
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.Video,
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920",
- IsRequired = true
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080",
- IsRequired = true
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30",
- IsRequired = true
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitrate,
- Value = "20000000",
- IsRequired = true
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "ac3,he-aac",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "6",
- IsRequired = true
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "aac",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "2",
- IsRequired = true
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Conditions = new[]
- {
- // The device does not have any audio switching capabilities
- new ProfileCondition
- {
- Condition = ProfileConditionType.Equals,
- Property = ProfileConditionValue.IsSecondaryAudio,
- Value = "false"
- }
- }
- }
- };
-
- ResponseProfiles = new[]
- {
- new ResponseProfile
- {
- Container = "mkv,ts,mpegts",
- Type = DlnaProfileType.Video,
- MimeType = "video/mp4"
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- }
- };
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/Foobar2000Profile.cs b/Emby.Dlna/Profiles/Foobar2000Profile.cs
deleted file mode 100644
index 68e959770..000000000
--- a/Emby.Dlna/Profiles/Foobar2000Profile.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class Foobar2000Profile : DefaultProfile
- {
- public Foobar2000Profile()
- {
- Name = "foobar2000";
-
- SupportedMediaTypes = "Audio";
-
- Identification = new DeviceIdentification
- {
- FriendlyName = @"foobar",
-
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Name = "User-Agent",
- Value = "foobar",
- Match = HeaderMatchType.Substring
- }
- }
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "mp3",
- AudioCodec = "mp2,mp3",
- Type = DlnaProfileType.Audio
- },
-
- new DirectPlayProfile
- {
- Container = "mp4",
- AudioCodec = "mp4",
- Type = DlnaProfileType.Audio
- },
-
- new DirectPlayProfile
- {
- Container = "aac,wav",
- Type = DlnaProfileType.Audio
- },
-
- new DirectPlayProfile
- {
- Container = "flac",
- AudioCodec = "flac",
- Type = DlnaProfileType.Audio
- },
-
- new DirectPlayProfile
- {
- Container = "asf",
- AudioCodec = "wmav2,wmapro,wmavoice",
- Type = DlnaProfileType.Audio
- },
-
- new DirectPlayProfile
- {
- Container = "ogg",
- AudioCodec = "vorbis",
- Type = DlnaProfileType.Audio
- }
- };
-
- ResponseProfiles = System.Array.Empty<ResponseProfile>();
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/LgTvProfile.cs b/Emby.Dlna/Profiles/LgTvProfile.cs
deleted file mode 100644
index fbb368d3e..000000000
--- a/Emby.Dlna/Profiles/LgTvProfile.cs
+++ /dev/null
@@ -1,211 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class LgTvProfile : DefaultProfile
- {
- public LgTvProfile()
- {
- Name = "LG Smart TV";
-
- TimelineOffsetSeconds = 10;
-
- Identification = new DeviceIdentification
- {
- FriendlyName = @"LG.*",
-
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Name = "User-Agent",
- Value = "LG",
- Match = HeaderMatchType.Substring
- }
- }
- };
-
- TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
- new TranscodingProfile
- {
- Container = "ts",
- AudioCodec = "ac3,aac,mp3",
- VideoCodec = "h264",
- Type = DlnaProfileType.Video
- },
- new TranscodingProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "ts,mpegts,avi,mkv,m2ts",
- VideoCodec = "h264",
- AudioCodec = "aac,ac3,eac3,mp3,dca,dts",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp4,m4v",
- VideoCodec = "h264,mpeg4",
- AudioCodec = "aac,ac3,eac3,mp3,dca,dts",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp3",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- ContainerProfiles = new[]
- {
- new ContainerProfile
- {
- Type = DlnaProfileType.Photo,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- }
- }
- }
- };
-
- CodecProfiles = new[]
- {
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "mpeg4",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "h264",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoLevel,
- Value = "41"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "ac3,eac3,aac,mp3",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "6"
- }
- }
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- },
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.External
- }
- };
-
- ResponseProfiles = new[]
- {
- new ResponseProfile
- {
- Container = "m4v",
- Type = DlnaProfileType.Video,
- MimeType = "video/mp4"
- },
- new ResponseProfile
- {
- Container = "ts,mpegts",
- Type = DlnaProfileType.Video,
- MimeType = "video/mpeg"
- }
- };
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/LinksysDMA2100Profile.cs b/Emby.Dlna/Profiles/LinksysDMA2100Profile.cs
deleted file mode 100644
index 1a510dfec..000000000
--- a/Emby.Dlna/Profiles/LinksysDMA2100Profile.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class LinksysDMA2100Profile : DefaultProfile
- {
- public LinksysDMA2100Profile()
- {
- // Linksys DMA2100us does not need any transcoding of the formats we support statically
- Name = "Linksys DMA2100";
-
- Identification = new DeviceIdentification
- {
- ModelName = "DMA2100us"
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "mp3,flac,m4a,wma",
- Type = DlnaProfileType.Audio
- },
-
- new DirectPlayProfile
- {
- Container = "avi,mp4,mkv,ts,mpegts,m4v",
- Type = DlnaProfileType.Video
- }
- };
-
- ResponseProfiles = new[]
- {
- new ResponseProfile
- {
- Container = "m4v",
- Type = DlnaProfileType.Video,
- MimeType = "video/mp4"
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- }
- };
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/MarantzProfile.cs b/Emby.Dlna/Profiles/MarantzProfile.cs
deleted file mode 100644
index 24078ab29..000000000
--- a/Emby.Dlna/Profiles/MarantzProfile.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class MarantzProfile : DefaultProfile
- {
- public MarantzProfile()
- {
- Name = "Marantz";
-
- SupportedMediaTypes = "Audio";
-
- Identification = new DeviceIdentification
- {
- Manufacturer = @"Marantz",
-
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Name = "User-Agent",
- Value = "Marantz",
- Match = HeaderMatchType.Substring
- }
- }
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "aac,mp3,wav,wma,flac",
- Type = DlnaProfileType.Audio
- },
- };
-
- ResponseProfiles = System.Array.Empty<ResponseProfile>();
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/MediaMonkeyProfile.cs b/Emby.Dlna/Profiles/MediaMonkeyProfile.cs
deleted file mode 100644
index 28d639b6d..000000000
--- a/Emby.Dlna/Profiles/MediaMonkeyProfile.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class MediaMonkeyProfile : DefaultProfile
- {
- public MediaMonkeyProfile()
- {
- Name = "MediaMonkey";
-
- SupportedMediaTypes = "Audio";
-
- Identification = new DeviceIdentification
- {
- FriendlyName = @"MediaMonkey",
-
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Name = "User-Agent",
- Value = "MediaMonkey",
- Match = HeaderMatchType.Substring
- }
- }
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "aac,mp3,mpa,wav,wma,mp2,ogg,oga,webma,ape,opus,flac,m4a",
- Type = DlnaProfileType.Audio
- }
- };
-
- ResponseProfiles = Array.Empty<ResponseProfile>();
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/PanasonicVieraProfile.cs b/Emby.Dlna/Profiles/PanasonicVieraProfile.cs
deleted file mode 100644
index 0d536acf3..000000000
--- a/Emby.Dlna/Profiles/PanasonicVieraProfile.cs
+++ /dev/null
@@ -1,222 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class PanasonicVieraProfile : DefaultProfile
- {
- public PanasonicVieraProfile()
- {
- Name = "Panasonic Viera";
-
- Identification = new DeviceIdentification
- {
- FriendlyName = @"VIERA",
- Manufacturer = "Panasonic",
-
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Name = "User-Agent",
- Value = "Panasonic MIL DLNA",
- Match = HeaderMatchType.Substring
- }
- }
- };
-
- AddXmlRootAttribute("xmlns:pv", "http://www.pv.com/pvns/");
-
- TimelineOffsetSeconds = 10;
-
- TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
- new TranscodingProfile
- {
- Container = "ts",
- AudioCodec = "ac3",
- VideoCodec = "h264",
- Type = DlnaProfileType.Video
- },
- new TranscodingProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "mpeg,mpg",
- VideoCodec = "mpeg2video,mpeg4",
- AudioCodec = "ac3,mp3,pcm_dvd",
- Type = DlnaProfileType.Video
- },
-
- new DirectPlayProfile
- {
- Container = "mkv",
- VideoCodec = "h264,mpeg2video",
- AudioCodec = "aac,ac3,dca,mp3,mp2,pcm,dts",
- Type = DlnaProfileType.Video
- },
-
- new DirectPlayProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264,mpeg2video",
- AudioCodec = "aac,mp3,mp2",
- Type = DlnaProfileType.Video
- },
-
- new DirectPlayProfile
- {
- Container = "mp4,m4v",
- VideoCodec = "h264",
- AudioCodec = "aac,ac3,mp3,pcm",
- Type = DlnaProfileType.Video
- },
-
- new DirectPlayProfile
- {
- Container = "mov",
- VideoCodec = "h264",
- AudioCodec = "aac,pcm",
- Type = DlnaProfileType.Video
- },
-
- new DirectPlayProfile
- {
- Container = "avi",
- VideoCodec = "mpeg4",
- AudioCodec = "pcm",
- Type = DlnaProfileType.Video
- },
-
- new DirectPlayProfile
- {
- Container = "flv",
- VideoCodec = "h264",
- AudioCodec = "aac",
- Type = DlnaProfileType.Video
- },
-
- new DirectPlayProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
-
- new DirectPlayProfile
- {
- Container = "mp4",
- AudioCodec = "aac",
- Type = DlnaProfileType.Audio
- },
-
- new DirectPlayProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- ContainerProfiles = new[]
- {
- new ContainerProfile
- {
- Type = DlnaProfileType.Photo,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- }
- }
- }
- };
-
- CodecProfiles = new[]
- {
- new CodecProfile
- {
- Type = CodecType.Video,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitDepth,
- Value = "8",
- IsRequired = false
- }
- }
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- },
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.External
- }
- };
-
- ResponseProfiles = new[]
- {
- new ResponseProfile
- {
- Type = DlnaProfileType.Video,
- Container = "ts,mpegts",
- OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
- MimeType = "video/vnd.dlna.mpeg-tts"
- },
- new ResponseProfile
- {
- Container = "m4v",
- Type = DlnaProfileType.Video,
- MimeType = "video/mp4"
- }
- };
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/PopcornHourProfile.cs b/Emby.Dlna/Profiles/PopcornHourProfile.cs
deleted file mode 100644
index 7fbf8c164..000000000
--- a/Emby.Dlna/Profiles/PopcornHourProfile.cs
+++ /dev/null
@@ -1,225 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class PopcornHourProfile : DefaultProfile
- {
- public PopcornHourProfile()
- {
- Name = "Popcorn Hour";
-
- TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
-
- new TranscodingProfile
- {
- Container = "mp4",
- Type = DlnaProfileType.Video,
- AudioCodec = "aac",
- VideoCodec = "h264"
- },
-
- new TranscodingProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "mp4,mov,m4v",
- Type = DlnaProfileType.Video,
- VideoCodec = "h264,mpeg4",
- AudioCodec = "aac"
- },
-
- new DirectPlayProfile
- {
- Container = "ts,mpegts",
- Type = DlnaProfileType.Video,
- VideoCodec = "h264",
- AudioCodec = "aac,ac3,eac3,mp3,mp2,pcm"
- },
-
- new DirectPlayProfile
- {
- Container = "asf,wmv",
- Type = DlnaProfileType.Video,
- VideoCodec = "wmv3,vc1",
- AudioCodec = "wmav2,wmapro"
- },
-
- new DirectPlayProfile
- {
- Container = "avi",
- Type = DlnaProfileType.Video,
- VideoCodec = "mpeg4,msmpeg4",
- AudioCodec = "mp3,ac3,eac3,mp2,pcm"
- },
-
- new DirectPlayProfile
- {
- Container = "mkv",
- Type = DlnaProfileType.Video,
- VideoCodec = "h264",
- AudioCodec = "aac,mp3,ac3,eac3,mp2,pcm"
- },
- new DirectPlayProfile
- {
- Container = "aac,mp3,flac,ogg,wma,wav",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "jpeg,gif,bmp,png",
- Type = DlnaProfileType.Photo
- }
- };
-
- CodecProfiles = new[]
- {
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "h264",
- Conditions = new[]
- {
- new ProfileCondition(ProfileConditionType.EqualsAny, ProfileConditionValue.VideoProfile, "baseline|constrained baseline"),
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.NotEquals,
- Property = ProfileConditionValue.IsAnamorphic,
- Value = "true",
- IsRequired = false
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.Video,
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.NotEquals,
- Property = ProfileConditionValue.IsAnamorphic,
- Value = "true",
- IsRequired = false
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "aac",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "2",
- IsRequired = false
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.Audio,
- Codec = "aac",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "2",
- IsRequired = false
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.Audio,
- Codec = "mp3",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "2",
- IsRequired = false
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioBitrate,
- Value = "320000",
- IsRequired = false
- }
- }
- }
- };
-
- ResponseProfiles = new[]
- {
- new ResponseProfile
- {
- Container = "m4v",
- Type = DlnaProfileType.Video,
- MimeType = "video/mp4"
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- }
- };
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/SamsungSmartTvProfile.cs b/Emby.Dlna/Profiles/SamsungSmartTvProfile.cs
deleted file mode 100644
index ddbebba2a..000000000
--- a/Emby.Dlna/Profiles/SamsungSmartTvProfile.cs
+++ /dev/null
@@ -1,367 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class SamsungSmartTvProfile : DefaultProfile
- {
- public SamsungSmartTvProfile()
- {
- Name = "Samsung Smart TV";
-
- EnableAlbumArtInDidl = true;
-
- // Without this, older samsungs fail to browse
- EnableSingleAlbumArtLimit = true;
-
- Identification = new DeviceIdentification
- {
- ModelUrl = "samsung.com",
-
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Name = "User-Agent",
- Value = @"SEC_",
- Match = HeaderMatchType.Substring
- }
- }
- };
-
- AddXmlRootAttribute("xmlns:sec", "http://www.sec.co.kr/");
-
- TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
- new TranscodingProfile
- {
- Container = "ts",
- AudioCodec = "ac3",
- VideoCodec = "h264",
- Type = DlnaProfileType.Video,
- EstimateContentLength = false
- },
- new TranscodingProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "asf",
- VideoCodec = "h264,mpeg4,mjpeg",
- AudioCodec = "mp3,ac3,wmav2,wmapro,wmavoice",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "avi",
- VideoCodec = "h264,mpeg4,mjpeg",
- AudioCodec = "mp3,ac3,dca,dts",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mkv",
- VideoCodec = "h264,mpeg4,mjpeg4",
- AudioCodec = "mp3,ac3,dca,aac,dts",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp4,m4v",
- VideoCodec = "h264,mpeg4",
- AudioCodec = "mp3,aac",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "3gp",
- VideoCodec = "h264,mpeg4",
- AudioCodec = "aac,he-aac",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mpg,mpeg",
- VideoCodec = "mpeg1video,mpeg2video,h264",
- AudioCodec = "ac3,mp2,mp3,aac",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "vro,vob",
- VideoCodec = "mpeg1video,mpeg2video",
- AudioCodec = "ac3,mp2,mp3",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "ts",
- VideoCodec = "mpeg2video,h264,vc1",
- AudioCodec = "ac3,aac,mp3,eac3",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "asf",
- VideoCodec = "wmv2,wmv3",
- AudioCodec = "wmav2,wmavoice",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp3,flac",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- ContainerProfiles = new[]
- {
- new ContainerProfile
- {
- Type = DlnaProfileType.Photo,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- }
- }
- }
- };
-
- CodecProfiles = new[]
- {
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "mpeg2video",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitrate,
- Value = "30720000"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "mpeg4",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitrate,
- Value = "8192000"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "h264",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitrate,
- Value = "37500000"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoLevel,
- Value = "41"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "wmv2,wmv3,vc1",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitrate,
- Value = "25600000"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "wmav2,dca,aac,mp3,dts",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "6"
- }
- }
- }
- };
-
- ResponseProfiles = new[]
- {
- new ResponseProfile
- {
- Container = "avi",
- MimeType = "video/x-msvideo",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "mkv",
- MimeType = "video/x-mkv",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "flac",
- MimeType = "audio/x-flac",
- Type = DlnaProfileType.Audio
- },
- new ResponseProfile
- {
- Container = "m4v",
- Type = DlnaProfileType.Video,
- MimeType = "video/mp4"
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- },
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.External,
- DidlMode = "CaptionInfoEx"
- }
- };
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/SharpSmartTvProfile.cs b/Emby.Dlna/Profiles/SharpSmartTvProfile.cs
deleted file mode 100644
index aa8d434e3..000000000
--- a/Emby.Dlna/Profiles/SharpSmartTvProfile.cs
+++ /dev/null
@@ -1,121 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class SharpSmartTvProfile : DefaultProfile
- {
- public SharpSmartTvProfile()
- {
- Name = "Sharp Smart TV";
-
- RequiresPlainFolders = true;
- RequiresPlainVideoItems = true;
-
- Identification = new DeviceIdentification
- {
- Manufacturer = "Sharp",
-
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Name = "User-Agent",
- Value = "Sharp",
- Match = HeaderMatchType.Substring
- }
- }
- };
-
- TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
-
- new TranscodingProfile
- {
- Container = "ts",
- Type = DlnaProfileType.Video,
- AudioCodec = "ac3,aac,mp3,dts,dca",
- VideoCodec = "h264",
- EnableMpegtsM2TsMode = true
- },
-
- new TranscodingProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "m4v,mkv,avi,mov,mp4",
- VideoCodec = "h264,mpeg4",
- AudioCodec = "aac,mp3,ac3,dts,dca",
- Type = DlnaProfileType.Video
- },
-
- new DirectPlayProfile
- {
- Container = "asf,wmv",
- Type = DlnaProfileType.Video
- },
-
- new DirectPlayProfile
- {
- Container = "mpg,mpeg",
- VideoCodec = "mpeg2video",
- AudioCodec = "mp3,aac",
- Type = DlnaProfileType.Video
- },
-
- new DirectPlayProfile
- {
- Container = "flv",
- VideoCodec = "h264",
- AudioCodec = "mp3,aac",
- Type = DlnaProfileType.Video
- },
-
- new DirectPlayProfile
- {
- Container = "mp3,wav",
- Type = DlnaProfileType.Audio
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- },
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.External
- }
- };
-
- ResponseProfiles = new[]
- {
- new ResponseProfile
- {
- Container = "m4v",
- Type = DlnaProfileType.Video,
- MimeType = "video/mp4"
- }
- };
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs
deleted file mode 100644
index 765c12504..000000000
--- a/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs
+++ /dev/null
@@ -1,230 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class SonyBlurayPlayer2013 : DefaultProfile
- {
- public SonyBlurayPlayer2013()
- {
- Name = "Sony Blu-ray Player 2013";
-
- Identification = new DeviceIdentification
- {
- ModelNumber = "BDP-2013",
-
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Name = "X-AV-Physical-Unit-Info",
- Value = "BDP-S1100",
- Match = HeaderMatchType.Substring
- },
- new HttpHeaderInfo
- {
- Name = "X-AV-Physical-Unit-Info",
- Value = "BDP-S3100",
- Match = HeaderMatchType.Substring
- },
- new HttpHeaderInfo
- {
- Name = "X-AV-Physical-Unit-Info",
- Value = "BDP-S5100",
- Match = HeaderMatchType.Substring
- },
- new HttpHeaderInfo
- {
- Name = "X-AV-Physical-Unit-Info",
- Value = "BDP-S6100",
- Match = HeaderMatchType.Substring
- },
- new HttpHeaderInfo
- {
- Name = "X-AV-Physical-Unit-Info",
- Value = "BDP-S7100",
- Match = HeaderMatchType.Substring
- }
- }
- };
-
- AddXmlRootAttribute("xmlns:av", "urn:schemas-sony-com:av");
-
- ModelName = "Windows Media Player Sharing";
- ModelNumber = "3.0";
- Manufacturer = "Microsoft Corporation";
-
- ProtocolInfo = "http-get:*:video/divx:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/flac:DLNA.ORG_PN=FLAC;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/ogg:DLNA.ORG_PN=OGG;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/gif:DLNA.ORG_PN=GIF_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_JP_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-flv:DLNA.ORG_PN=FLV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-dvr:DLNA.ORG_PN=DVR_MS;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/wtv:DLNA.ORG_PN=WTV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/ogg:DLNA.ORG_PN=OGV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.rn-realvideo:DLNA.ORG_PN=REAL_VIDEO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_3GPP_P0_L10_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_MP4_P0_L10_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000";
-
- TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
-
- new TranscodingProfile
- {
- Container = "mkv",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- Type = DlnaProfileType.Video
- },
-
- new TranscodingProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "mpeg1video,mpeg2video,h264",
- AudioCodec = "ac3,aac,mp3,pcm",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mpeg,mpg",
- VideoCodec = "mpeg1video,mpeg2video",
- AudioCodec = "ac3,mp3,mp2,pcm",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp4,m4v",
- VideoCodec = "mpeg4,h264",
- AudioCodec = "ac3,aac,pcm,mp3",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "avi",
- VideoCodec = "mpeg4,h264",
- AudioCodec = "ac3,aac,mp3,pcm",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mkv",
- VideoCodec = "mpeg4,h264",
- AudioCodec = "ac3,dca,aac,mp3,pcm,dts",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "m2ts,mts",
- VideoCodec = "h264,mpeg4,vc1",
- AudioCodec = "aac,mp3,ac3,dca,dts",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "wmv,asf",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp3,m4a,wma,wav",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "jpeg,png,gif",
- Type = DlnaProfileType.Photo
- }
- };
-
- CodecProfiles = new[]
- {
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "h264",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30",
- IsRequired = false
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "ac3",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "6",
- IsRequired = false
- }
- }
- }
- };
-
- ContainerProfiles = new[]
- {
- new ContainerProfile
- {
- Type = DlnaProfileType.Photo,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- }
- }
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- }
- };
-
- ResponseProfiles = Array.Empty<ResponseProfile>();
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs
deleted file mode 100644
index 390c8a3e4..000000000
--- a/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs
+++ /dev/null
@@ -1,230 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class SonyBlurayPlayer2014 : DefaultProfile
- {
- public SonyBlurayPlayer2014()
- {
- Name = "Sony Blu-ray Player 2014";
-
- Identification = new DeviceIdentification
- {
- ModelNumber = "BDP-2014",
-
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Name = "X-AV-Physical-Unit-Info",
- Value = "BDP-S1200",
- Match = HeaderMatchType.Substring
- },
- new HttpHeaderInfo
- {
- Name = "X-AV-Physical-Unit-Info",
- Value = "BDP-S3200",
- Match = HeaderMatchType.Substring
- },
- new HttpHeaderInfo
- {
- Name = "X-AV-Physical-Unit-Info",
- Value = "BDP-S5200",
- Match = HeaderMatchType.Substring
- },
- new HttpHeaderInfo
- {
- Name = "X-AV-Physical-Unit-Info",
- Value = "BDP-S6200",
- Match = HeaderMatchType.Substring
- },
- new HttpHeaderInfo
- {
- Name = "X-AV-Physical-Unit-Info",
- Value = "BDP-S7200",
- Match = HeaderMatchType.Substring
- }
- }
- };
-
- AddXmlRootAttribute("xmlns:av", "urn:schemas-sony-com:av");
-
- ModelName = "Windows Media Player Sharing";
- ModelNumber = "3.0";
- Manufacturer = "Microsoft Corporation";
-
- ProtocolInfo = "http-get:*:video/divx:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/flac:DLNA.ORG_PN=FLAC;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/ogg:DLNA.ORG_PN=OGG;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/gif:DLNA.ORG_PN=GIF_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_JP_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-flv:DLNA.ORG_PN=FLV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-dvr:DLNA.ORG_PN=DVR_MS;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/wtv:DLNA.ORG_PN=WTV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/ogg:DLNA.ORG_PN=OGV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.rn-realvideo:DLNA.ORG_PN=REAL_VIDEO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_3GPP_P0_L10_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_MP4_P0_L10_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000";
-
- TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
-
- new TranscodingProfile
- {
- Container = "mkv",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- Type = DlnaProfileType.Video
- },
-
- new TranscodingProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "mpeg1video,mpeg2video,h264",
- AudioCodec = "ac3,aac,mp3,pcm",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mpeg,mpg",
- VideoCodec = "mpeg1video,mpeg2video",
- AudioCodec = "ac3,mp3,mp2,pcm",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp4,m4v",
- VideoCodec = "mpeg4,h264",
- AudioCodec = "ac3,aac,pcm,mp3",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "avi",
- VideoCodec = "mpeg4,h264",
- AudioCodec = "ac3,aac,mp3,pcm",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mkv",
- VideoCodec = "mpeg4,h264",
- AudioCodec = "ac3,dca,aac,mp3,pcm,dts",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "m2ts,mts",
- VideoCodec = "h264,mpeg4,vc1",
- AudioCodec = "aac,mp3,ac3,dca,dts",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "wmv,asf",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp3,m4a,wma,wav",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "jpeg,png,gif",
- Type = DlnaProfileType.Photo
- }
- };
-
- CodecProfiles = new[]
- {
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "h264",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30",
- IsRequired = false
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "ac3",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "6",
- IsRequired = false
- }
- }
- }
- };
-
- ContainerProfiles = new[]
- {
- new ContainerProfile
- {
- Type = DlnaProfileType.Photo,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- }
- }
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- }
- };
-
- ResponseProfiles = Array.Empty<ResponseProfile>();
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs
deleted file mode 100644
index 25adc4d02..000000000
--- a/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs
+++ /dev/null
@@ -1,218 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class SonyBlurayPlayer2015 : DefaultProfile
- {
- public SonyBlurayPlayer2015()
- {
- Name = "Sony Blu-ray Player 2015";
-
- Identification = new DeviceIdentification
- {
- ModelNumber = "BDP-2015",
-
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Name = "X-AV-Physical-Unit-Info",
- Value = "BDP-S1500",
- Match = HeaderMatchType.Substring
- },
- new HttpHeaderInfo
- {
- Name = "X-AV-Physical-Unit-Info",
- Value = "BDP-S3500",
- Match = HeaderMatchType.Substring
- },
- new HttpHeaderInfo
- {
- Name = "X-AV-Physical-Unit-Info",
- Value = "BDP-S6500",
- Match = HeaderMatchType.Substring
- }
- }
- };
-
- AddXmlRootAttribute("xmlns:av", "urn:schemas-sony-com:av");
-
- ModelName = "Windows Media Player Sharing";
- ModelNumber = "3.0";
- Manufacturer = "Microsoft Corporation";
-
- ProtocolInfo = "http-get:*:video/divx:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/flac:DLNA.ORG_PN=FLAC;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/ogg:DLNA.ORG_PN=OGG;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/gif:DLNA.ORG_PN=GIF_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_JP_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-flv:DLNA.ORG_PN=FLV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-dvr:DLNA.ORG_PN=DVR_MS;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/wtv:DLNA.ORG_PN=WTV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/ogg:DLNA.ORG_PN=OGV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.rn-realvideo:DLNA.ORG_PN=REAL_VIDEO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_3GPP_P0_L10_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_MP4_P0_L10_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000";
-
- TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
-
- new TranscodingProfile
- {
- Container = "mkv",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- Type = DlnaProfileType.Video
- },
-
- new TranscodingProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "mpeg1video,mpeg2video,h264",
- AudioCodec = "ac3,aac,mp3,pcm",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mpeg,mpg",
- VideoCodec = "mpeg1video,mpeg2video",
- AudioCodec = "ac3,mp3,mp2,pcm",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp4,m4v",
- VideoCodec = "mpeg4,h264",
- AudioCodec = "ac3,aac,pcm,mp3",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "avi",
- VideoCodec = "mpeg4,h264",
- AudioCodec = "ac3,aac,mp3,pcm",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mkv",
- VideoCodec = "mpeg4,h264",
- AudioCodec = "ac3,dca,aac,mp3,pcm,dts",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "m2ts,mts",
- VideoCodec = "h264,mpeg4,vc1",
- AudioCodec = "aac,mp3,ac3,dca,dts",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "wmv,asf",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp3,m4a,wma,wav",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "jpeg,png,gif",
- Type = DlnaProfileType.Photo
- }
- };
-
- CodecProfiles = new[]
- {
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "h264",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30",
- IsRequired = false
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "ac3",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "6",
- IsRequired = false
- }
- }
- }
- };
-
- ContainerProfiles = new[]
- {
- new ContainerProfile
- {
- Type = DlnaProfileType.Photo,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- }
- }
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- }
- };
-
- ResponseProfiles = Array.Empty<ResponseProfile>();
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs
deleted file mode 100644
index 0a39a5f40..000000000
--- a/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs
+++ /dev/null
@@ -1,218 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class SonyBlurayPlayer2016 : DefaultProfile
- {
- public SonyBlurayPlayer2016()
- {
- Name = "Sony Blu-ray Player 2016";
-
- Identification = new DeviceIdentification
- {
- ModelNumber = "BDP-2016",
-
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Name = "X-AV-Physical-Unit-Info",
- Value = "BDP-S1700",
- Match = HeaderMatchType.Substring
- },
- new HttpHeaderInfo
- {
- Name = "X-AV-Physical-Unit-Info",
- Value = "BDP-S3700",
- Match = HeaderMatchType.Substring
- },
- new HttpHeaderInfo
- {
- Name = "X-AV-Physical-Unit-Info",
- Value = "BDP-S6700",
- Match = HeaderMatchType.Substring
- }
- }
- };
-
- AddXmlRootAttribute("xmlns:av", "urn:schemas-sony-com:av");
-
- ModelName = "Windows Media Player Sharing";
- ModelNumber = "3.0";
- Manufacturer = "Microsoft Corporation";
-
- ProtocolInfo = "http-get:*:video/divx:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/flac:DLNA.ORG_PN=FLAC;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/ogg:DLNA.ORG_PN=OGG;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/gif:DLNA.ORG_PN=GIF_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_JP_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-flv:DLNA.ORG_PN=FLV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-dvr:DLNA.ORG_PN=DVR_MS;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/wtv:DLNA.ORG_PN=WTV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/ogg:DLNA.ORG_PN=OGV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.rn-realvideo:DLNA.ORG_PN=REAL_VIDEO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_3GPP_P0_L10_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_MP4_P0_L10_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000";
-
- TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
-
- new TranscodingProfile
- {
- Container = "mkv",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- Type = DlnaProfileType.Video
- },
-
- new TranscodingProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "mpeg1video,mpeg2video,h264",
- AudioCodec = "ac3,aac,mp3,pcm",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mpeg,mpg",
- VideoCodec = "mpeg1video,mpeg2video",
- AudioCodec = "ac3,mp3,mp2,pcm",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp4,m4v",
- VideoCodec = "mpeg4,h264",
- AudioCodec = "ac3,aac,pcm,mp3",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "avi",
- VideoCodec = "mpeg4,h264",
- AudioCodec = "ac3,aac,mp3,pcm",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mkv",
- VideoCodec = "mpeg4,h264",
- AudioCodec = "ac3,dca,aac,mp3,pcm,dts",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "m2ts,mts",
- VideoCodec = "h264,mpeg4,vc1",
- AudioCodec = "aac,mp3,ac3,dca,dts",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "wmv,asf",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp3,m4a,wma,wav",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "jpeg,png,gif",
- Type = DlnaProfileType.Photo
- }
- };
-
- CodecProfiles = new[]
- {
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "h264",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30",
- IsRequired = false
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "ac3",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "6",
- IsRequired = false
- }
- }
- }
- };
-
- ContainerProfiles = new[]
- {
- new ContainerProfile
- {
- Type = DlnaProfileType.Photo,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- }
- }
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- }
- };
-
- ResponseProfiles = Array.Empty<ResponseProfile>();
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs b/Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs
deleted file mode 100644
index 05c8ab1c1..000000000
--- a/Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs
+++ /dev/null
@@ -1,284 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class SonyBlurayPlayerProfile : DefaultProfile
- {
- public SonyBlurayPlayerProfile()
- {
- Name = "Sony Blu-ray Player";
-
- Identification = new DeviceIdentification
- {
- FriendlyName = @"Blu-ray Disc Player",
- Manufacturer = "Sony",
-
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Name = "X-AV-Client-Info",
- Value = @"(Blu-ray Disc Player|Home Theater System|Home Theatre System|Media Player)",
- Match = HeaderMatchType.Regex
- },
-
- new HttpHeaderInfo
- {
- Name = "X-AV-Physical-Unit-Info",
- Value = @"(Blu-ray Disc Player|Home Theater System|Home Theatre System|Media Player)",
- Match = HeaderMatchType.Regex
- }
- }
- };
-
- AddXmlRootAttribute("xmlns:av", "urn:schemas-sony-com:av");
-
- ModelName = "Windows Media Player Sharing";
- ModelNumber = "3.0";
- Manufacturer = "Microsoft Corporation";
-
- ProtocolInfo = "http-get:*:video/divx:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/flac:DLNA.ORG_PN=FLAC;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/ogg:DLNA.ORG_PN=OGG;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/gif:DLNA.ORG_PN=GIF_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_JP_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-flv:DLNA.ORG_PN=FLV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-dvr:DLNA.ORG_PN=DVR_MS;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/wtv:DLNA.ORG_PN=WTV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/ogg:DLNA.ORG_PN=OGV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.rn-realvideo:DLNA.ORG_PN=REAL_VIDEO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_3GPP_P0_L10_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_MP4_P0_L10_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000";
-
- TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
-
- new TranscodingProfile
- {
- Container = "ts",
- VideoCodec = "mpeg2video",
- AudioCodec = "ac3",
- Type = DlnaProfileType.Video
- },
-
- new TranscodingProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "mpeg1video,mpeg2video,h264",
- AudioCodec = "ac3,aac,mp3,pcm",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mpeg",
- VideoCodec = "mpeg1video,mpeg2video",
- AudioCodec = "ac3,mp3,pcm",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "avi,mp4,m4v",
- VideoCodec = "mpeg4,h264",
- AudioCodec = "ac3,aac,mp3,pcm",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "asf",
- AudioCodec = "wmav2,wmapro,wmavoice",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- CodecProfiles = new[]
- {
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "h264",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30",
- IsRequired = false
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoLevel,
- Value = "41",
- IsRequired = false
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitrate,
- Value = "15360000",
- IsRequired = false
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "ac3",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "6",
- IsRequired = false
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "aac",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "2",
- IsRequired = false
- }
- }
- }
- };
-
- ContainerProfiles = new[]
- {
- new ContainerProfile
- {
- Type = DlnaProfileType.Photo,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- }
- }
- }
- };
-
- ResponseProfiles = new[]
- {
- new ResponseProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264,mpeg4,vc1",
- AudioCodec = "ac3,aac,mp3",
- MimeType = "video/vnd.dlna.mpeg-tts",
- OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "avi",
- MimeType = "video/mpeg",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "mkv",
- MimeType = "video/vnd.dlna.mpeg-tts",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "ts,mpegts",
- MimeType = "video/vnd.dlna.mpeg-tts",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "mp4",
- MimeType = "video/mpeg",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "m4v",
- MimeType = "video/mpeg",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "mpeg",
- MimeType = "video/mpeg",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "mp3",
- MimeType = "audio/mpeg",
- Type = DlnaProfileType.Audio
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- }
- };
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/SonyBravia2010Profile.cs b/Emby.Dlna/Profiles/SonyBravia2010Profile.cs
deleted file mode 100644
index 9f0d82b8f..000000000
--- a/Emby.Dlna/Profiles/SonyBravia2010Profile.cs
+++ /dev/null
@@ -1,366 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class SonyBravia2010Profile : DefaultProfile
- {
- public SonyBravia2010Profile()
- {
- Name = "Sony Bravia (2010)";
-
- Identification = new DeviceIdentification
- {
- FriendlyName = @"KDL-[0-9]{2}[EHLNPB]X[0-9][01][0-9].*",
- Manufacturer = "Sony",
-
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Name = "X-AV-Client-Info",
- Value = @".*KDL-[0-9]{2}[EHLNPB]X[0-9][01][0-9].*",
- Match = HeaderMatchType.Regex
- }
- }
- };
-
- AddXmlRootAttribute("xmlns:av", "urn:schemas-sony-com:av");
-
- AlbumArtPn = "JPEG_TN";
-
- ModelName = "Windows Media Player Sharing";
- ModelNumber = "3.0";
- ModelUrl = "http://www.microsoft.com/";
- Manufacturer = "Microsoft Corporation";
- ManufacturerUrl = "http://www.microsoft.com/";
- SonyAggregationFlags = "10";
- ProtocolInfo =
- "http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000";
-
- EnableSingleAlbumArtLimit = true;
- EnableAlbumArtInDidl = true;
-
- TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
- new TranscodingProfile
- {
- Container = "ts",
- VideoCodec = "h264",
- AudioCodec = "ac3",
- Type = DlnaProfileType.Video,
- EnableMpegtsM2TsMode = true
- },
- new TranscodingProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "mpeg1video,mpeg2video",
- AudioCodec = "mp3,mp2",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mpeg",
- VideoCodec = "mpeg2video,mpeg1video",
- AudioCodec = "mp3,mp2",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- }
- };
-
- ResponseProfiles = new[]
- {
- new ResponseProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- MimeType = "video/vnd.dlna.mpeg-tts",
- OrgPn = "AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
- Type = DlnaProfileType.Video,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.Equals,
- Property = ProfileConditionValue.PacketLength,
- Value = "192"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.Equals,
- Property = ProfileConditionValue.VideoTimestamp,
- Value = "Valid"
- }
- }
- },
-
- new ResponseProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- MimeType = "video/mpeg",
- OrgPn = "AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
- Type = DlnaProfileType.Video,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.Equals,
- Property = ProfileConditionValue.PacketLength,
- Value = "188"
- }
- }
- },
-
- new ResponseProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- MimeType = "video/vnd.dlna.mpeg-tts",
- OrgPn = "AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "mpeg2video",
- MimeType = "video/vnd.dlna.mpeg-tts",
- OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "mpeg",
- VideoCodec = "mpeg1video,mpeg2video",
- MimeType = "video/mpeg",
- OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL",
- Type = DlnaProfileType.Video
- }
- };
-
- ContainerProfiles = new[]
- {
- new ContainerProfile
- {
- Type = DlnaProfileType.Photo,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- }
- }
- }
- };
-
- CodecProfiles = new[]
- {
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "h264",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitrate,
- Value = "20000000"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoLevel,
- Value = "41"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "mpeg2video",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitrate,
- Value = "20000000"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.Video,
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "ac3",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "6"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "aac",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "2"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.NotEquals,
- Property = ProfileConditionValue.AudioProfile,
- Value = "he-aac"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "mp3,mp2",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "2"
- }
- }
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- }
- };
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/SonyBravia2011Profile.cs b/Emby.Dlna/Profiles/SonyBravia2011Profile.cs
deleted file mode 100644
index dfb91817a..000000000
--- a/Emby.Dlna/Profiles/SonyBravia2011Profile.cs
+++ /dev/null
@@ -1,389 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class SonyBravia2011Profile : DefaultProfile
- {
- public SonyBravia2011Profile()
- {
- Name = "Sony Bravia (2011)";
-
- Identification = new DeviceIdentification
- {
- FriendlyName = @"KDL-[0-9]{2}([A-Z]X[0-9]2[0-9]|CX400).*",
- Manufacturer = "Sony",
-
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Name = "X-AV-Client-Info",
- Value = @".*KDL-[0-9]{2}([A-Z]X[0-9]2[0-9]|CX400).*",
- Match = HeaderMatchType.Regex
- }
- }
- };
-
- AddXmlRootAttribute("xmlns:av", "urn:schemas-sony-com:av");
-
- AlbumArtPn = "JPEG_TN";
-
- ModelName = "Windows Media Player Sharing";
- ModelNumber = "3.0";
- ModelUrl = "http://www.microsoft.com/";
- Manufacturer = "Microsoft Corporation";
- ManufacturerUrl = "http://www.microsoft.com/";
- SonyAggregationFlags = "10";
- EnableSingleAlbumArtLimit = true;
- EnableAlbumArtInDidl = true;
-
- TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
- new TranscodingProfile
- {
- Container = "ts",
- VideoCodec = "h264",
- AudioCodec = "ac3",
- Type = DlnaProfileType.Video,
- EnableMpegtsM2TsMode = true
- },
- new TranscodingProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "mpeg2video",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp4,m4v",
- VideoCodec = "h264,mpeg4",
- AudioCodec = "ac3,aac,mp3",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mpeg",
- VideoCodec = "mpeg2video,mpeg1video",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "asf",
- VideoCodec = "wmv2,wmv3,vc1",
- AudioCodec = "wmav2,wmapro,wmavoice",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "asf",
- AudioCodec = "wmav2,wmapro,wmavoice",
- Type = DlnaProfileType.Audio
- }
- };
-
- ContainerProfiles = new[]
- {
- new ContainerProfile
- {
- Type = DlnaProfileType.Photo,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- }
- }
- }
- };
-
- ResponseProfiles = new[]
- {
- new ResponseProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- MimeType = "video/vnd.dlna.mpeg-tts",
- OrgPn = "AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
- Type = DlnaProfileType.Video,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.Equals,
- Property = ProfileConditionValue.PacketLength,
- Value = "192"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.Equals,
- Property = ProfileConditionValue.VideoTimestamp,
- Value = "Valid"
- }
- }
- },
-
- new ResponseProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- MimeType = "video/mpeg",
- OrgPn = "AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
- Type = DlnaProfileType.Video,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.Equals,
- Property = ProfileConditionValue.PacketLength,
- Value = "188"
- }
- }
- },
-
- new ResponseProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- MimeType = "video/vnd.dlna.mpeg-tts",
- OrgPn = "AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "mpeg2video",
- MimeType = "video/vnd.dlna.mpeg-tts",
- OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "mpeg",
- VideoCodec = "mpeg1video,mpeg2video",
- MimeType = "video/mpeg",
- OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL",
- Type = DlnaProfileType.Video
- },
- new ResponseProfile
- {
- Container = "m4v",
- Type = DlnaProfileType.Video,
- MimeType = "video/mp4"
- }
- };
-
- CodecProfiles = new[]
- {
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "h264",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitrate,
- Value = "20000000"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoLevel,
- Value = "41"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "mpeg2video",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitrate,
- Value = "20000000"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.Video,
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "ac3",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "6"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "aac",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "2"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.NotEquals,
- Property = ProfileConditionValue.AudioProfile,
- Value = "he-aac"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "mp3,mp2",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "2"
- }
- }
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- }
- };
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/SonyBravia2012Profile.cs b/Emby.Dlna/Profiles/SonyBravia2012Profile.cs
deleted file mode 100644
index d59ee38d7..000000000
--- a/Emby.Dlna/Profiles/SonyBravia2012Profile.cs
+++ /dev/null
@@ -1,307 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class SonyBravia2012Profile : DefaultProfile
- {
- public SonyBravia2012Profile()
- {
- Name = "Sony Bravia (2012)";
-
- Identification = new DeviceIdentification
- {
- FriendlyName = @"KDL-[0-9]{2}[A-Z]X[0-9]5([0-9]|G).*",
- Manufacturer = "Sony",
-
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Name = "X-AV-Client-Info",
- Value = @".*KDL-[0-9]{2}[A-Z]X[0-9]5([0-9]|G).*",
- Match = HeaderMatchType.Regex
- }
- }
- };
-
- AddXmlRootAttribute("xmlns:av", "urn:schemas-sony-com:av");
-
- AlbumArtPn = "JPEG_TN";
-
- ModelName = "Windows Media Player Sharing";
- ModelNumber = "3.0";
- ModelUrl = "http://www.microsoft.com/";
- Manufacturer = "Microsoft Corporation";
- ManufacturerUrl = "http://www.microsoft.com/";
- SonyAggregationFlags = "10";
- EnableSingleAlbumArtLimit = true;
- EnableAlbumArtInDidl = true;
-
- TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
- new TranscodingProfile
- {
- Container = "ts",
- VideoCodec = "h264",
- AudioCodec = "ac3",
- Type = DlnaProfileType.Video,
- EnableMpegtsM2TsMode = true
- },
- new TranscodingProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "mpeg2video",
- AudioCodec = "mp3,mp2",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp4,m4v",
- VideoCodec = "h264,mpeg4",
- AudioCodec = "ac3,aac,mp3,mp2",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "avi",
- VideoCodec = "mpeg4",
- AudioCodec = "ac3,mp3",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mpeg",
- VideoCodec = "mpeg2video,mpeg1video",
- AudioCodec = "mp3,mp2",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "asf",
- VideoCodec = "wmv2,wmv3,vc1",
- AudioCodec = "wmav2,wmapro,wmavoice",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "asf",
- AudioCodec = "wmav2,wmapro,wmavoice",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- ResponseProfiles = new[]
- {
- new ResponseProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- MimeType = "video/vnd.dlna.mpeg-tts",
- OrgPn = "AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
- Type = DlnaProfileType.Video,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.Equals,
- Property = ProfileConditionValue.PacketLength,
- Value = "192"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.Equals,
- Property = ProfileConditionValue.VideoTimestamp,
- Value = "Valid"
- }
- }
- },
-
- new ResponseProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- MimeType = "video/mpeg",
- OrgPn = "AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
- Type = DlnaProfileType.Video,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.Equals,
- Property = ProfileConditionValue.PacketLength,
- Value = "188"
- }
- }
- },
-
- new ResponseProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- MimeType = "video/vnd.dlna.mpeg-tts",
- OrgPn = "AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "mpeg2video",
- MimeType = "video/vnd.dlna.mpeg-tts",
- OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "mpeg",
- VideoCodec = "mpeg1video,mpeg2video",
- MimeType = "video/mpeg",
- OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL",
- Type = DlnaProfileType.Video
- },
- new ResponseProfile
- {
- Container = "m4v",
- Type = DlnaProfileType.Video,
- MimeType = "video/mp4"
- }
- };
-
- ContainerProfiles = new[]
- {
- new ContainerProfile
- {
- Type = DlnaProfileType.Photo,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- }
- }
- }
- };
-
- CodecProfiles = new[]
- {
- new CodecProfile
- {
- Type = CodecType.Video,
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "ac3",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "6"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "mp3,mp2",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "2"
- }
- }
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- }
- };
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/SonyBravia2013Profile.cs b/Emby.Dlna/Profiles/SonyBravia2013Profile.cs
deleted file mode 100644
index 73b0fd67e..000000000
--- a/Emby.Dlna/Profiles/SonyBravia2013Profile.cs
+++ /dev/null
@@ -1,324 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class SonyBravia2013Profile : DefaultProfile
- {
- public SonyBravia2013Profile()
- {
- Name = "Sony Bravia (2013)";
-
- Identification = new DeviceIdentification
- {
- FriendlyName = @"KDL-[0-9]{2}[WR][5689][0-9]{2}A.*",
- Manufacturer = "Sony",
-
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Name = "X-AV-Client-Info",
- Value = @".*KDL-[0-9]{2}[WR][5689][0-9]{2}A.*",
- Match = HeaderMatchType.Regex
- }
- }
- };
-
- AddXmlRootAttribute("xmlns:av", "urn:schemas-sony-com:av");
-
- AlbumArtPn = "JPEG_TN";
-
- ModelName = "Windows Media Player Sharing";
- ModelNumber = "3.0";
- ModelUrl = "http://www.microsoft.com/";
- Manufacturer = "Microsoft Corporation";
- ManufacturerUrl = "http://www.microsoft.com/";
- SonyAggregationFlags = "10";
- EnableSingleAlbumArtLimit = true;
- EnableAlbumArtInDidl = true;
-
- TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Container = "mp3",
- Type = DlnaProfileType.Audio
- },
- new TranscodingProfile
- {
- Container = "ts",
- VideoCodec = "h264",
- AudioCodec = "ac3",
- Type = DlnaProfileType.Video,
- EnableMpegtsM2TsMode = true
- },
- new TranscodingProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264",
- AudioCodec = "ac3,eac3,aac,mp3",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "mpeg2video",
- AudioCodec = "mp3,mp2",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp4,m4v",
- VideoCodec = "h264,mpeg4",
- AudioCodec = "ac3,eac3,aac,mp3,mp2",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mov",
- VideoCodec = "h264,mpeg4,mjpeg",
- AudioCodec = "ac3,eac3,aac,mp3,mp2",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mkv",
- VideoCodec = "h264,mpeg4,vp8",
- AudioCodec = "ac3,eac3,aac,mp3,mp2,pcm,vorbis",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "avi",
- VideoCodec = "mpeg4",
- AudioCodec = "ac3,eac3,mp3",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "avi",
- VideoCodec = "mjpeg",
- AudioCodec = "pcm",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mpeg",
- VideoCodec = "mpeg2video,mpeg1video",
- AudioCodec = "mp3,mp2",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "asf",
- VideoCodec = "wmv2,wmv3,vc1",
- AudioCodec = "wmav2,wmapro,wmavoice",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "mp4",
- AudioCodec = "aac",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "wav",
- AudioCodec = "pcm",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "asf",
- AudioCodec = "wmav2,wmapro,wmavoice",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- ContainerProfiles = new[]
- {
- new ContainerProfile
- {
- Type = DlnaProfileType.Photo,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- }
- }
- }
- };
-
- ResponseProfiles = new[]
- {
- new ResponseProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- MimeType = "video/vnd.dlna.mpeg-tts",
- OrgPn = "AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
- Type = DlnaProfileType.Video,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.Equals,
- Property = ProfileConditionValue.PacketLength,
- Value = "192"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.Equals,
- Property = ProfileConditionValue.VideoTimestamp,
- Value = "Valid"
- }
- }
- },
-
- new ResponseProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- MimeType = "video/mpeg",
- OrgPn = "AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
- Type = DlnaProfileType.Video,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.Equals,
- Property = ProfileConditionValue.PacketLength,
- Value = "188"
- }
- }
- },
-
- new ResponseProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- MimeType = "video/vnd.dlna.mpeg-tts",
- OrgPn = "AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "mpeg2video",
- MimeType = "video/vnd.dlna.mpeg-tts",
- OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "mpeg",
- VideoCodec = "mpeg1video,mpeg2video",
- MimeType = "video/mpeg",
- OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL",
- Type = DlnaProfileType.Video
- },
- new ResponseProfile
- {
- Container = "m4v",
- Type = DlnaProfileType.Video,
- MimeType = "video/mp4"
- }
- };
-
- CodecProfiles = new[]
- {
- new CodecProfile
- {
- Type = CodecType.Video,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "mp3,mp2",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "2"
- }
- }
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- }
- };
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/SonyBravia2014Profile.cs b/Emby.Dlna/Profiles/SonyBravia2014Profile.cs
deleted file mode 100644
index db8ee5750..000000000
--- a/Emby.Dlna/Profiles/SonyBravia2014Profile.cs
+++ /dev/null
@@ -1,324 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class SonyBravia2014Profile : DefaultProfile
- {
- public SonyBravia2014Profile()
- {
- Name = "Sony Bravia (2014)";
-
- Identification = new DeviceIdentification
- {
- FriendlyName = @"(KDL-[0-9]{2}W[5-9][0-9]{2}B|KDL-[0-9]{2}R480|XBR-[0-9]{2}X[89][0-9]{2}B|KD-[0-9]{2}[SX][89][0-9]{3}B).*",
- Manufacturer = "Sony",
-
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Name = "X-AV-Client-Info",
- Value = @".*(KDL-[0-9]{2}W[5-9][0-9]{2}B|KDL-[0-9]{2}R480|XBR-[0-9]{2}X[89][0-9]{2}B|KD-[0-9]{2}[SX][89][0-9]{3}B).*",
- Match = HeaderMatchType.Regex
- }
- }
- };
-
- AddXmlRootAttribute("xmlns:av", "urn:schemas-sony-com:av");
-
- AlbumArtPn = "JPEG_TN";
-
- ModelName = "Windows Media Player Sharing";
- ModelNumber = "3.0";
- ModelUrl = "http://www.microsoft.com/";
- Manufacturer = "Microsoft Corporation";
- ManufacturerUrl = "http://www.microsoft.com/";
- SonyAggregationFlags = "10";
- EnableSingleAlbumArtLimit = true;
- EnableAlbumArtInDidl = true;
-
- TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Container = "mp3",
- Type = DlnaProfileType.Audio
- },
- new TranscodingProfile
- {
- Container = "ts",
- VideoCodec = "h264",
- AudioCodec = "ac3",
- Type = DlnaProfileType.Video,
- EnableMpegtsM2TsMode = true
- },
- new TranscodingProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264",
- AudioCodec = "ac3,eac3,aac,mp3",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "mpeg2video",
- AudioCodec = "mp3,mp2",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp4,m4v",
- VideoCodec = "h264,mpeg4",
- AudioCodec = "ac3,eac3,aac,mp3,mp2",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mov",
- VideoCodec = "h264,mpeg4,mjpeg",
- AudioCodec = "ac3,eac3,aac,mp3,mp2",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mkv",
- VideoCodec = "h264,mpeg4,vp8",
- AudioCodec = "ac3,eac3,aac,mp3,mp2,pcm,vorbis",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "avi",
- VideoCodec = "mpeg4",
- AudioCodec = "ac3,eac3,mp3",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "avi",
- VideoCodec = "mjpeg",
- AudioCodec = "pcm",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mpeg",
- VideoCodec = "mpeg2video,mpeg1video",
- AudioCodec = "mp3,mp2",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "asf",
- VideoCodec = "wmv2,wmv3,vc1",
- AudioCodec = "wmav2,wmapro,wmavoice",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "mp4",
- AudioCodec = "aac",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "wav",
- AudioCodec = "pcm",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "asf",
- AudioCodec = "wmav2,wmapro,wmavoice",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- ContainerProfiles = new[]
- {
- new ContainerProfile
- {
- Type = DlnaProfileType.Photo,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- }
- }
- }
- };
-
- ResponseProfiles = new[]
- {
- new ResponseProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- MimeType = "video/vnd.dlna.mpeg-tts",
- OrgPn = "AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
- Type = DlnaProfileType.Video,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.Equals,
- Property = ProfileConditionValue.PacketLength,
- Value = "192"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.Equals,
- Property = ProfileConditionValue.VideoTimestamp,
- Value = "Valid"
- }
- }
- },
-
- new ResponseProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- MimeType = "video/mpeg",
- OrgPn = "AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
- Type = DlnaProfileType.Video,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.Equals,
- Property = ProfileConditionValue.PacketLength,
- Value = "188"
- }
- }
- },
-
- new ResponseProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264",
- AudioCodec = "ac3,aac,mp3",
- MimeType = "video/vnd.dlna.mpeg-tts",
- OrgPn = "AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "mpeg2video",
- MimeType = "video/vnd.dlna.mpeg-tts",
- OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "mpeg",
- VideoCodec = "mpeg1video,mpeg2video",
- MimeType = "video/mpeg",
- OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL",
- Type = DlnaProfileType.Video
- },
- new ResponseProfile
- {
- Container = "m4v",
- Type = DlnaProfileType.Video,
- MimeType = "video/mp4"
- }
- };
-
- CodecProfiles = new[]
- {
- new CodecProfile
- {
- Type = CodecType.Video,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "mp3,mp2",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "2"
- }
- }
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- }
- };
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/SonyPs3Profile.cs b/Emby.Dlna/Profiles/SonyPs3Profile.cs
deleted file mode 100644
index e4a7a3a59..000000000
--- a/Emby.Dlna/Profiles/SonyPs3Profile.cs
+++ /dev/null
@@ -1,269 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class SonyPs3Profile : DefaultProfile
- {
- public SonyPs3Profile()
- {
- Name = "Sony PlayStation 3";
-
- Identification = new DeviceIdentification
- {
- FriendlyName = "PLAYSTATION 3",
-
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Name = "User-Agent",
- Value = @"PLAYSTATION 3",
- Match = HeaderMatchType.Substring
- },
-
- new HttpHeaderInfo
- {
- Name = "X-AV-Client-Info",
- Value = @"PLAYSTATION 3",
- Match = HeaderMatchType.Substring
- }
- }
- };
-
- AlbumArtPn = "JPEG_TN";
-
- SonyAggregationFlags = "10";
- EnableSingleAlbumArtLimit = true;
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "avi",
- Type = DlnaProfileType.Video,
- VideoCodec = "mpeg4",
- AudioCodec = "mp2,mp3"
- },
- new DirectPlayProfile
- {
- Container = "ts,mpegts",
- Type = DlnaProfileType.Video,
- VideoCodec = "mpeg1video,mpeg2video,h264",
- AudioCodec = "aac,ac3,mp2"
- },
- new DirectPlayProfile
- {
- Container = "mpeg",
- Type = DlnaProfileType.Video,
- VideoCodec = "mpeg1video,mpeg2video",
- AudioCodec = "mp2"
- },
- new DirectPlayProfile
- {
- Container = "mp4",
- Type = DlnaProfileType.Video,
- VideoCodec = "h264,mpeg4",
- AudioCodec = "aac,ac3"
- },
- new DirectPlayProfile
- {
- Container = "aac,mp3,wav",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "jpeg,png,gif,bmp,tiff",
- Type = DlnaProfileType.Photo
- }
- };
-
- TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
- new TranscodingProfile
- {
- Container = "ts",
- VideoCodec = "h264",
- AudioCodec = "aac,ac3,mp2",
- Type = DlnaProfileType.Video
- },
- new TranscodingProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- ContainerProfiles = new[]
- {
- new ContainerProfile
- {
- Type = DlnaProfileType.Photo,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- }
- }
- }
- };
-
- CodecProfiles = new[]
- {
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "h264",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30",
- IsRequired = false
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitrate,
- Value = "15360000",
- IsRequired = false
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoLevel,
- Value = "41",
- IsRequired = false
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "ac3",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "6",
- IsRequired = false
- },
-
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioBitrate,
- Value = "640000",
- IsRequired = false
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "wmapro",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "2"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "aac",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.NotEquals,
- Property = ProfileConditionValue.AudioProfile,
- Value = "he-aac",
- IsRequired = false
- }
- }
- }
- };
-
- ResponseProfiles = new[]
- {
- new ResponseProfile
- {
- Container = "mp4,mov",
- AudioCodec = "aac",
- MimeType = "video/mp4",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "avi",
- MimeType = "video/divx",
- OrgPn = "AVI",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "wav",
- MimeType = "audio/wav",
- Type = DlnaProfileType.Audio
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- }
- };
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/SonyPs4Profile.cs b/Emby.Dlna/Profiles/SonyPs4Profile.cs
deleted file mode 100644
index 985df0c9a..000000000
--- a/Emby.Dlna/Profiles/SonyPs4Profile.cs
+++ /dev/null
@@ -1,278 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class SonyPs4Profile : DefaultProfile
- {
- public SonyPs4Profile()
- {
- Name = "Sony PlayStation 4";
-
- Identification = new DeviceIdentification
- {
- FriendlyName = "PLAYSTATION 4",
-
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Name = "User-Agent",
- Value = @"PLAYSTATION 4",
- Match = HeaderMatchType.Substring
- },
-
- new HttpHeaderInfo
- {
- Name = "X-AV-Client-Info",
- Value = @"PLAYSTATION 4",
- Match = HeaderMatchType.Substring
- }
- }
- };
-
- AlbumArtPn = "JPEG_TN";
-
- SonyAggregationFlags = "10";
- EnableSingleAlbumArtLimit = true;
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "avi",
- Type = DlnaProfileType.Video,
- VideoCodec = "mpeg4",
- AudioCodec = "mp2,mp3"
- },
- new DirectPlayProfile
- {
- Container = "ts,mpegts",
- Type = DlnaProfileType.Video,
- VideoCodec = "mpeg1video,mpeg2video,h264",
- AudioCodec = "aac,ac3,mp2"
- },
- new DirectPlayProfile
- {
- Container = "mpeg",
- Type = DlnaProfileType.Video,
- VideoCodec = "mpeg1video,mpeg2video",
- AudioCodec = "mp2"
- },
- new DirectPlayProfile
- {
- Container = "mp4,mkv,m4v",
- Type = DlnaProfileType.Video,
- VideoCodec = "h264,mpeg4",
- AudioCodec = "aac,ac3"
- },
- new DirectPlayProfile
- {
- Container = "aac,mp3,wav",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "jpeg,png,gif,bmp,tiff",
- Type = DlnaProfileType.Photo
- }
- };
-
- TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio,
- // Transcoded audio won't be playable at all without this
- TranscodeSeekInfo = TranscodeSeekInfo.Bytes
- },
- new TranscodingProfile
- {
- Container = "ts",
- VideoCodec = "h264",
- AudioCodec = "aac,ac3,mp2",
- Type = DlnaProfileType.Video
- },
- new TranscodingProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- ContainerProfiles = new[]
- {
- new ContainerProfile
- {
- Type = DlnaProfileType.Photo,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- }
- }
- }
- };
-
- CodecProfiles = new[]
- {
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "h264",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30",
- IsRequired = false
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitrate,
- Value = "15360000",
- IsRequired = false
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoLevel,
- Value = "41",
- IsRequired = false
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "ac3",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "6",
- IsRequired = false
- },
-
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioBitrate,
- Value = "640000",
- IsRequired = false
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "wmapro",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "2"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "aac",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.NotEquals,
- Property = ProfileConditionValue.AudioProfile,
- Value = "he-aac",
- IsRequired = false
- }
- }
- }
- };
-
- ResponseProfiles = new[]
- {
- new ResponseProfile
- {
- Container = "mp4,mov",
- AudioCodec = "aac",
- MimeType = "video/mp4",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "avi",
- MimeType = "video/divx",
- OrgPn = "AVI",
- Type = DlnaProfileType.Video
- },
-
- new ResponseProfile
- {
- Container = "wav",
- MimeType = "audio/wav",
- Type = DlnaProfileType.Audio
- },
-
- new ResponseProfile
- {
- Container = "m4v",
- Type = DlnaProfileType.Video,
- MimeType = "video/mp4"
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- }
- };
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/WdtvLiveProfile.cs b/Emby.Dlna/Profiles/WdtvLiveProfile.cs
deleted file mode 100644
index 937ca0f42..000000000
--- a/Emby.Dlna/Profiles/WdtvLiveProfile.cs
+++ /dev/null
@@ -1,266 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class WdtvLiveProfile : DefaultProfile
- {
- public WdtvLiveProfile()
- {
- Name = "WDTV Live";
-
- TimelineOffsetSeconds = 5;
- IgnoreTranscodeByteRangeRequests = true;
-
- Identification = new DeviceIdentification
- {
- ModelName = "WD TV",
-
- Headers = new[]
- {
- new HttpHeaderInfo { Name = "User-Agent", Value = "alphanetworks", Match = HeaderMatchType.Substring },
- new HttpHeaderInfo
- {
- Name = "User-Agent",
- Value = "ALPHA Networks",
- Match = HeaderMatchType.Substring
- }
- }
- };
-
- TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Container = "mp3",
- Type = DlnaProfileType.Audio,
- AudioCodec = "mp3"
- },
- new TranscodingProfile
- {
- Container = "ts",
- Type = DlnaProfileType.Video,
- VideoCodec = "h264",
- AudioCodec = "aac"
- },
- new TranscodingProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "avi",
- Type = DlnaProfileType.Video,
- VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1",
- AudioCodec = "ac3,eac3,dca,mp2,mp3,pcm,dts"
- },
-
- new DirectPlayProfile
- {
- Container = "mpeg",
- Type = DlnaProfileType.Video,
- VideoCodec = "mpeg1video,mpeg2video",
- AudioCodec = "ac3,eac3,dca,mp2,mp3,pcm,dts"
- },
-
- new DirectPlayProfile
- {
- Container = "mkv",
- Type = DlnaProfileType.Video,
- VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1",
- AudioCodec = "ac3,eac3,dca,aac,mp2,mp3,pcm,dts"
- },
-
- new DirectPlayProfile
- {
- Container = "ts,m2ts,mpegts",
- Type = DlnaProfileType.Video,
- VideoCodec = "mpeg1video,mpeg2video,h264,vc1",
- AudioCodec = "ac3,eac3,dca,mp2,mp3,aac,dts"
- },
-
- new DirectPlayProfile
- {
- Container = "mp4,mov,m4v",
- Type = DlnaProfileType.Video,
- VideoCodec = "h264,mpeg4",
- AudioCodec = "ac3,eac3,aac,mp2,mp3,dca,dts"
- },
-
- new DirectPlayProfile
- {
- Container = "asf",
- Type = DlnaProfileType.Video,
- VideoCodec = "vc1",
- AudioCodec = "wmav2,wmapro"
- },
-
- new DirectPlayProfile
- {
- Container = "asf",
- Type = DlnaProfileType.Video,
- VideoCodec = "mpeg2video",
- AudioCodec = "mp2,ac3"
- },
-
- new DirectPlayProfile
- {
- Container = "mp3",
- AudioCodec = "mp2,mp3",
- Type = DlnaProfileType.Audio
- },
-
- new DirectPlayProfile
- {
- Container = "mp4",
- AudioCodec = "mp4",
- Type = DlnaProfileType.Audio
- },
-
- new DirectPlayProfile
- {
- Container = "flac",
- Type = DlnaProfileType.Audio
- },
-
- new DirectPlayProfile
- {
- Container = "asf",
- AudioCodec = "wmav2,wmapro,wmavoice",
- Type = DlnaProfileType.Audio
- },
-
- new DirectPlayProfile
- {
- Container = "ogg",
- AudioCodec = "vorbis",
- Type = DlnaProfileType.Audio
- },
-
- new DirectPlayProfile
- {
- Type = DlnaProfileType.Photo,
-
- Container = "jpeg,png,gif,bmp,tiff"
- }
- };
-
- ResponseProfiles = new[]
- {
- new ResponseProfile
- {
- Container = "ts,mpegts",
- OrgPn = "MPEG_TS_SD_NA",
- Type = DlnaProfileType.Video
- }
- };
-
- ContainerProfiles = new[]
- {
- new ContainerProfile
- {
- Type = DlnaProfileType.Photo,
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- }
- }
- }
- };
-
- CodecProfiles = new[]
- {
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "h264",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoLevel,
- Value = "41"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "aac",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "2"
- }
- }
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.External
- },
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- },
- new SubtitleProfile
- {
- Format = "sub",
- Method = SubtitleDeliveryMethod.Embed
- },
- new SubtitleProfile
- {
- Format = "subrip",
- Method = SubtitleDeliveryMethod.Embed
- },
- new SubtitleProfile
- {
- Format = "idx",
- Method = SubtitleDeliveryMethod.Embed
- }
- };
- }
- }
-}
diff --git a/Emby.Dlna/Profiles/XboxOneProfile.cs b/Emby.Dlna/Profiles/XboxOneProfile.cs
deleted file mode 100644
index 84d8184a2..000000000
--- a/Emby.Dlna/Profiles/XboxOneProfile.cs
+++ /dev/null
@@ -1,372 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.Profiles
-{
- [System.Xml.Serialization.XmlRoot("Profile")]
- public class XboxOneProfile : DefaultProfile
- {
- public XboxOneProfile()
- {
- Name = "Xbox One";
-
- TimelineOffsetSeconds = 40;
-
- Identification = new DeviceIdentification
- {
- ModelName = "Xbox One",
-
- Headers = new[]
- {
- new HttpHeaderInfo
- {
- Name = "FriendlyName.DLNA.ORG", Value = "XboxOne", Match = HeaderMatchType.Substring
- },
- new HttpHeaderInfo
- {
- Name = "User-Agent", Value = "NSPlayer/12", Match = HeaderMatchType.Substring
- }
- }
- };
-
- var videoProfile = "high|main|baseline|constrained baseline";
- var videoLevel = "41";
-
- TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
- new TranscodingProfile
- {
- Container = "jpeg",
- VideoCodec = "jpeg",
- Type = DlnaProfileType.Photo
- },
- new TranscodingProfile
- {
- Container = "ts",
- VideoCodec = "h264",
- AudioCodec = "aac",
- Type = DlnaProfileType.Video
- }
- };
-
- DirectPlayProfiles = new[]
- {
- new DirectPlayProfile
- {
- Container = "ts,mpegts",
- VideoCodec = "h264,mpeg2video,hevc",
- AudioCodec = "ac3,aac,mp3",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "avi",
- VideoCodec = "mpeg4",
- AudioCodec = "ac3,mp3",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "avi",
- VideoCodec = "h264",
- AudioCodec = "aac",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "mp4,mov,mkv,m4v",
- VideoCodec = "h264,mpeg4,mpeg2video,hevc",
- AudioCodec = "aac,ac3",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "asf",
- VideoCodec = "wmv2,wmv3,vc1",
- AudioCodec = "wmav2,wmapro",
- Type = DlnaProfileType.Video
- },
- new DirectPlayProfile
- {
- Container = "asf",
- AudioCodec = "wmav2,wmapro,wmavoice",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "mp3",
- AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
- },
- new DirectPlayProfile
- {
- Container = "jpeg",
- Type = DlnaProfileType.Photo
- }
- };
-
- ContainerProfiles = new[]
- {
- new ContainerProfile
- {
- Type = DlnaProfileType.Video,
- Container = "mp4,mov",
-
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.Equals,
- Property = ProfileConditionValue.Has64BitOffsets,
- Value = "false",
- IsRequired = false
- }
- }
- }
- };
-
- CodecProfiles = new[]
- {
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "mpeg4",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.NotEquals,
- Property = ProfileConditionValue.IsAnamorphic,
- Value = "true",
- IsRequired = false
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitDepth,
- Value = "8",
- IsRequired = false
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30",
- IsRequired = false
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitrate,
- Value = "5120000",
- IsRequired = false
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "h264",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.NotEquals,
- Property = ProfileConditionValue.IsAnamorphic,
- Value = "true",
- IsRequired = false
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitDepth,
- Value = "8",
- IsRequired = false
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoLevel,
- Value = videoLevel,
- IsRequired = false
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.EqualsAny,
- Property = ProfileConditionValue.VideoProfile,
- Value = videoProfile,
- IsRequired = false
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.Video,
- Codec = "wmv2,wmv3,vc1",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.NotEquals,
- Property = ProfileConditionValue.IsAnamorphic,
- Value = "true",
- IsRequired = false
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitDepth,
- Value = "8",
- IsRequired = false
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Width,
- Value = "1920"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.Height,
- Value = "1080"
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoFramerate,
- Value = "30",
- IsRequired = false
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitrate,
- Value = "15360000",
- IsRequired = false
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.Video,
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.NotEquals,
- Property = ProfileConditionValue.IsAnamorphic,
- Value = "true",
- IsRequired = false
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitDepth,
- Value = "8",
- IsRequired = false
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "ac3,wmav2,wmapro",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "6",
- IsRequired = false
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.VideoAudio,
- Codec = "aac",
- Conditions = new[]
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.AudioChannels,
- Value = "2",
- IsRequired = false
- },
- new ProfileCondition
- {
- Condition = ProfileConditionType.Equals,
- Property = ProfileConditionValue.AudioProfile,
- Value = "lc",
- IsRequired = false
- }
- }
- }
- };
-
- ResponseProfiles = new[]
- {
- new ResponseProfile
- {
- Container = "avi",
- MimeType = "video/avi",
- Type = DlnaProfileType.Video
- },
- new ResponseProfile
- {
- Container = "m4v",
- Type = DlnaProfileType.Video,
- MimeType = "video/mp4"
- }
- };
-
- SubtitleProfiles = new[]
- {
- new SubtitleProfile
- {
- Format = "srt",
- Method = SubtitleDeliveryMethod.Embed
- }
- };
- }
- }
-}
diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs
index 7bec2eb72..9c423b395 100644
--- a/Emby.Dlna/Service/BaseControlHandler.cs
+++ b/Emby.Dlna/Service/BaseControlHandler.cs
@@ -6,8 +6,8 @@ using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
-using Diacritics.Extensions;
using Emby.Dlna.Didl;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Configuration;
using Microsoft.Extensions.Logging;
diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj
index f1a6d451b..8f64b0b21 100644
--- a/Emby.Drawing/Emby.Drawing.csproj
+++ b/Emby.Drawing/Emby.Drawing.csproj
@@ -32,7 +32,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs
index 18b413964..11256dafd 100644
--- a/Emby.Drawing/ImageProcessor.cs
+++ b/Emby.Drawing/ImageProcessor.cs
@@ -395,7 +395,13 @@ namespace Emby.Drawing
public string GetImageBlurHash(string path)
{
var size = GetImageDimensions(path);
- if (size.Width <= 0 || size.Height <= 0)
+ return GetImageBlurHash(path, size);
+ }
+
+ /// <inheritdoc />
+ public string GetImageBlurHash(string path, ImageDimensions imageDimensions)
+ {
+ if (imageDimensions.Width <= 0 || imageDimensions.Height <= 0)
{
return string.Empty;
}
@@ -403,8 +409,8 @@ namespace Emby.Drawing
// We want tiles to be as close to square as possible, and to *mostly* keep under 16 tiles for performance.
// One tile is (width / xComp) x (height / yComp) pixels, which means that ideally yComp = xComp * height / width.
// See more at https://github.com/woltapp/blurhash/#how-do-i-pick-the-number-of-x-and-y-components
- float xCompF = MathF.Sqrt(16.0f * size.Width / size.Height);
- float yCompF = xCompF * size.Height / size.Width;
+ float xCompF = MathF.Sqrt(16.0f * imageDimensions.Width / imageDimensions.Height);
+ float yCompF = xCompF * imageDimensions.Height / imageDimensions.Width;
int xComp = Math.Min((int)xCompF + 1, 9);
int yComp = Math.Min((int)yCompF + 1, 9);
@@ -439,47 +445,46 @@ namespace Emby.Drawing
.ToString("N", CultureInfo.InvariantCulture);
}
- private async Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
+ private Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
{
- var inputFormat = Path.GetExtension(originalImagePath)
- .TrimStart('.')
- .Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase);
+ var inputFormat = Path.GetExtension(originalImagePath.AsSpan()).TrimStart('.').ToString();
// These are just jpg files renamed as tbn
if (string.Equals(inputFormat, "tbn", StringComparison.OrdinalIgnoreCase))
{
- return (originalImagePath, dateModified);
- }
-
- if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat))
- {
- try
- {
- string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
-
- string cacheExtension = _mediaEncoder.SupportsEncoder("libwebp") ? ".webp" : ".png";
- var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
-
- var file = _fileSystem.GetFileInfo(outputPath);
- if (!file.Exists)
- {
- await _mediaEncoder.ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
- dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath);
- }
- else
- {
- dateModified = file.LastWriteTimeUtc;
- }
-
- originalImagePath = outputPath;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Image conversion failed for {Path}", originalImagePath);
- }
- }
-
- return (originalImagePath, dateModified);
+ return Task.FromResult((originalImagePath, dateModified));
+ }
+
+ // TODO _mediaEncoder.ConvertImage is not implemented
+ // if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat))
+ // {
+ // try
+ // {
+ // string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
+ //
+ // string cacheExtension = _mediaEncoder.SupportsEncoder("libwebp") ? ".webp" : ".png";
+ // var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
+ //
+ // var file = _fileSystem.GetFileInfo(outputPath);
+ // if (!file.Exists)
+ // {
+ // await _mediaEncoder.ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
+ // dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath);
+ // }
+ // else
+ // {
+ // dateModified = file.LastWriteTimeUtc;
+ // }
+ //
+ // originalImagePath = outputPath;
+ // }
+ // catch (Exception ex)
+ // {
+ // _logger.LogError(ex, "Image conversion failed for {Path}", originalImagePath);
+ // }
+ // }
+
+ return Task.FromResult((originalImagePath, dateModified));
}
/// <summary>
diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs b/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs
index 48ab8b57d..ae8c8a39b 100644
--- a/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs
+++ b/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs
@@ -3,7 +3,7 @@ namespace Emby.Naming.AudioBook
/// <summary>
/// Data object for passing result of audiobook part/chapter extraction.
/// </summary>
- public struct AudioBookFilePathParserResult
+ public record struct AudioBookFilePathParserResult
{
/// <summary>
/// Gets or sets optional number of path extracted from audiobook filename.
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index 961efa48e..e016d7e51 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -48,7 +48,6 @@ namespace Emby.Naming.Common
".mkv",
".mk3d",
".mov",
- ".mp2",
".mp4",
".mpe",
".mpeg",
@@ -315,7 +314,7 @@ namespace Emby.Naming.Common
// This isn't a Kodi naming rule, but the expression below causes false positives,
// so we make sure this one gets tested first.
// "Foo Bar 889"
- new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/x]*$")
+ new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>[0-9]{1,4})(-(?<endingepnumber>[0-9]{2,4}))*[^\\\/x]*$")
{
IsNamed = true
},
diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj
index ea7309b13..ca002b981 100644
--- a/Emby.Naming/Emby.Naming.csproj
+++ b/Emby.Naming/Emby.Naming.csproj
@@ -36,7 +36,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Naming</PackageId>
- <VersionPrefix>10.8.0</VersionPrefix>
+ <VersionPrefix>10.9.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
@@ -52,7 +52,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/Emby.Notifications/Emby.Notifications.csproj b/Emby.Notifications/Emby.Notifications.csproj
index e96789d50..b797a5194 100644
--- a/Emby.Notifications/Emby.Notifications.csproj
+++ b/Emby.Notifications/Emby.Notifications.csproj
@@ -28,7 +28,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj
index 549a4e32d..e1688dc6e 100644
--- a/Emby.Photos/Emby.Photos.csproj
+++ b/Emby.Photos/Emby.Photos.csproj
@@ -15,7 +15,7 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="TagLibSharp" Version="2.2.0" />
+ <PackageReference Include="TagLibSharp" Version="2.3.0" />
</ItemGroup>
<PropertyGroup>
@@ -30,7 +30,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
index 19fe0b108..26b4649dd 100644
--- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
+++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
@@ -210,10 +210,7 @@ namespace Emby.Server.Implementations.AppBase
/// <exception cref="ArgumentNullException"><c>newConfiguration</c> is <c>null</c>.</exception>
public virtual void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration)
{
- if (newConfiguration == null)
- {
- throw new ArgumentNullException(nameof(newConfiguration));
- }
+ ArgumentNullException.ThrowIfNull(newConfiguration);
ValidateCachePath(newConfiguration);
@@ -365,11 +362,7 @@ namespace Emby.Server.Implementations.AppBase
validatingStore.Validate(currentConfiguration, configuration);
}
- NamedConfigurationUpdating?.Invoke(this, new ConfigurationUpdateEventArgs
- {
- Key = key,
- NewConfiguration = configuration
- });
+ NamedConfigurationUpdating?.Invoke(this, new ConfigurationUpdateEventArgs(key, configuration));
_configurations.AddOrUpdate(key, configuration, (_, _) => configuration);
@@ -391,11 +384,13 @@ namespace Emby.Server.Implementations.AppBase
/// <param name="configuration">The old configuration.</param>
protected virtual void OnNamedConfigurationUpdated(string key, object configuration)
{
- NamedConfigurationUpdated?.Invoke(this, new ConfigurationUpdateEventArgs
- {
- Key = key,
- NewConfiguration = configuration
- });
+ NamedConfigurationUpdated?.Invoke(this, new ConfigurationUpdateEventArgs(key, configuration));
+ }
+
+ /// <inheritdoc />
+ public ConfigurationStore[] GetConfigurationStores()
+ {
+ return _configurationStores;
}
/// <inheritdoc />
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 82294644b..9a9de1059 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -83,6 +83,7 @@ using MediaBrowser.Controller.SyncPlay;
using MediaBrowser.Controller.TV;
using MediaBrowser.LocalMetadata.Savers;
using MediaBrowser.MediaEncoding.BdInfo;
+using MediaBrowser.MediaEncoding.Subtitles;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Globalization;
@@ -111,7 +112,7 @@ namespace Emby.Server.Implementations
/// <summary>
/// Class CompositionRoot.
/// </summary>
- public abstract class ApplicationHost : IServerApplicationHost, IDisposable
+ public abstract class ApplicationHost : IServerApplicationHost, IAsyncDisposable, IDisposable
{
/// <summary>
/// The environment variable prefixes to log at server startup.
@@ -629,12 +630,11 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
- serviceCollection.AddScoped<ISessionContext, SessionContext>();
-
serviceCollection.AddSingleton<IAuthService, AuthService>();
serviceCollection.AddSingleton<IQuickConnect, QuickConnectManager>();
- serviceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>();
+ serviceCollection.AddSingleton<ISubtitleParser, SubtitleEditParser>();
+ serviceCollection.AddSingleton<ISubtitleEncoder, SubtitleEncoder>();
serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>();
@@ -1114,13 +1114,13 @@ namespace Emby.Server.Implementations
}
/// <inheritdoc/>
- public string GetApiUrlForLocalAccess(bool allowHttps = true)
+ public string GetApiUrlForLocalAccess(IPObject hostname = null, bool allowHttps = true)
{
// With an empty source, the port will be null
- string smart = NetManager.GetBindInterface(string.Empty, out _);
+ var smart = NetManager.GetBindInterface(hostname ?? IPHost.None, out _);
var scheme = !allowHttps ? Uri.UriSchemeHttp : null;
int? port = !allowHttps ? HttpPort : null;
- return GetLocalApiUrl(smart.Trim('/'), scheme, port);
+ return GetLocalApiUrl(smart, scheme, port);
}
/// <inheritdoc/>
@@ -1134,11 +1134,13 @@ namespace Emby.Server.Implementations
// NOTE: If no BaseUrl is set then UriBuilder appends a trailing slash, but if there is no BaseUrl it does
// not. For consistency, always trim the trailing slash.
+ scheme ??= ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
+ var isHttps = string.Equals(scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase);
return new UriBuilder
{
- Scheme = scheme ?? (ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp),
+ Scheme = scheme,
Host = hostname,
- Port = port ?? (ListenWithHttps ? HttpsPort : HttpPort),
+ Port = port ?? (isHttps ? HttpsPort : HttpPort),
Path = ConfigurationManager.GetNetworkConfiguration().BaseUrl
}.ToString().TrimEnd('/');
}
@@ -1230,5 +1232,49 @@ namespace Emby.Server.Implementations
_disposed = true;
}
+
+ public async ValueTask DisposeAsync()
+ {
+ await DisposeAsyncCore().ConfigureAwait(false);
+ Dispose(false);
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// Used to perform asynchronous cleanup of managed resources or for cascading calls to <see cref="DisposeAsync"/>.
+ /// </summary>
+ /// <returns>A ValueTask.</returns>
+ protected virtual async ValueTask DisposeAsyncCore()
+ {
+ var type = GetType();
+
+ Logger.LogInformation("Disposing {Type}", type.Name);
+
+ foreach (var (part, _) in _disposableParts)
+ {
+ var partType = part.GetType();
+ if (partType == type)
+ {
+ continue;
+ }
+
+ Logger.LogInformation("Disposing {Type}", partType.Name);
+
+ try
+ {
+ part.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Error disposing {Type}", partType.Name);
+ }
+ }
+
+ // used for closing websockets
+ foreach (var session in _sessionManager.Sessions)
+ {
+ await session.DisposeAsync().ConfigureAwait(false);
+ }
+ }
}
}
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 92a85e862..6837cce5c 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -1188,10 +1188,7 @@ namespace Emby.Server.Implementations.Channels
internal IChannel GetChannelProvider(Channel channel)
{
- if (channel == null)
- {
- throw new ArgumentNullException(nameof(channel));
- }
+ ArgumentNullException.ThrowIfNull(channel);
var result = GetAllChannels()
.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(channel.ChannelId) || string.Equals(i.Name, channel.Name, StringComparison.OrdinalIgnoreCase));
diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs
index 381eb92a8..736b8125d 100644
--- a/Emby.Server.Implementations/Data/SqliteExtensions.cs
+++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs
@@ -54,10 +54,7 @@ namespace Emby.Server.Implementations.Data
public static void RunQueries(this SQLiteDatabaseConnection connection, string[] queries)
{
- if (queries == null)
- {
- throw new ArgumentNullException(nameof(queries));
- }
+ ArgumentNullException.ThrowIfNull(queries);
connection.RunInTransaction(conn =>
{
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index 5729a70ac..9c9fa7383 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -11,7 +11,6 @@ using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
-using Diacritics.Extensions;
using Emby.Server.Implementations.Playlists;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
@@ -171,7 +170,15 @@ namespace Emby.Server.Implementations.Data
"CodecTimeBase",
"ColorPrimaries",
"ColorSpace",
- "ColorTransfer"
+ "ColorTransfer",
+ "DvVersionMajor",
+ "DvVersionMinor",
+ "DvProfile",
+ "DvLevel",
+ "RpuPresentFlag",
+ "ElPresentFlag",
+ "BlPresentFlag",
+ "DvBlSignalCompatibilityId"
};
private static readonly string _mediaStreamSaveColumnsInsertQuery =
@@ -342,7 +349,7 @@ namespace Emby.Server.Implementations.Data
public void Initialize(SqliteUserDataRepository userDataRepo, IUserManager userManager)
{
const string CreateMediaStreamsTableCommand
- = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, ColorPrimaries TEXT NULL, ColorSpace TEXT NULL, ColorTransfer TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))";
+ = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, ColorPrimaries TEXT NULL, ColorSpace TEXT NULL, ColorTransfer TEXT NULL, DvVersionMajor INT NULL, DvVersionMinor INT NULL, DvProfile INT NULL, DvLevel INT NULL, RpuPresentFlag INT NULL, ElPresentFlag INT NULL, BlPresentFlag INT NULL, DvBlSignalCompatibilityId INT NULL, PRIMARY KEY (ItemId, StreamIndex))";
const string CreateMediaAttachmentsTableCommand
= "create table if not exists mediaattachments (ItemId GUID, AttachmentIndex INT, Codec TEXT, CodecTag TEXT NULL, Comment TEXT NULL, Filename TEXT NULL, MIMEType TEXT NULL, PRIMARY KEY (ItemId, AttachmentIndex))";
@@ -556,6 +563,15 @@ namespace Emby.Server.Implementations.Data
AddColumn(db, "MediaStreams", "ColorPrimaries", "TEXT", existingColumnNames);
AddColumn(db, "MediaStreams", "ColorSpace", "TEXT", existingColumnNames);
AddColumn(db, "MediaStreams", "ColorTransfer", "TEXT", existingColumnNames);
+
+ AddColumn(db, "MediaStreams", "DvVersionMajor", "INT", existingColumnNames);
+ AddColumn(db, "MediaStreams", "DvVersionMinor", "INT", existingColumnNames);
+ AddColumn(db, "MediaStreams", "DvProfile", "INT", existingColumnNames);
+ AddColumn(db, "MediaStreams", "DvLevel", "INT", existingColumnNames);
+ AddColumn(db, "MediaStreams", "RpuPresentFlag", "INT", existingColumnNames);
+ AddColumn(db, "MediaStreams", "ElPresentFlag", "INT", existingColumnNames);
+ AddColumn(db, "MediaStreams", "BlPresentFlag", "INT", existingColumnNames);
+ AddColumn(db, "MediaStreams", "DvBlSignalCompatibilityId", "INT", existingColumnNames);
},
TransactionMode);
@@ -567,10 +583,7 @@ namespace Emby.Server.Implementations.Data
public void SaveImages(BaseItem item)
{
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
+ ArgumentNullException.ThrowIfNull(item);
CheckDisposed();
@@ -601,10 +614,7 @@ namespace Emby.Server.Implementations.Data
/// </exception>
public void SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken)
{
- if (items == null)
- {
- throw new ArgumentNullException(nameof(items));
- }
+ ArgumentNullException.ThrowIfNull(items);
cancellationToken.ThrowIfCancellationRequested();
@@ -2069,10 +2079,7 @@ namespace Emby.Server.Implementations.Data
throw new ArgumentNullException(nameof(id));
}
- if (chapters == null)
- {
- throw new ArgumentNullException(nameof(chapters));
- }
+ ArgumentNullException.ThrowIfNull(chapters);
var idBlob = id.ToByteArray();
@@ -2404,7 +2411,7 @@ namespace Emby.Server.Implementations.Data
}
// genres, tags, studios, person, year?
- builder.Append("+ (Select count(1) * 10 from ItemValues where ItemId=Guid and CleanValue in (select CleanValue from itemvalues where ItemId=@SimilarItemId))");
+ builder.Append("+ (Select count(1) * 10 from ItemValues where ItemId=Guid and CleanValue in (select CleanValue from ItemValues where ItemId=@SimilarItemId))");
if (item is MusicArtist)
{
@@ -2541,10 +2548,7 @@ namespace Emby.Server.Implementations.Data
public int GetCount(InternalItemsQuery query)
{
- if (query == null)
- {
- throw new ArgumentNullException(nameof(query));
- }
+ ArgumentNullException.ThrowIfNull(query);
CheckDisposed();
@@ -2597,10 +2601,7 @@ namespace Emby.Server.Implementations.Data
public List<BaseItem> GetItemList(InternalItemsQuery query)
{
- if (query == null)
- {
- throw new ArgumentNullException(nameof(query));
- }
+ ArgumentNullException.ThrowIfNull(query);
CheckDisposed();
@@ -2778,10 +2779,7 @@ namespace Emby.Server.Implementations.Data
public QueryResult<BaseItem> GetItems(InternalItemsQuery query)
{
- if (query == null)
- {
- throw new ArgumentNullException(nameof(query));
- }
+ ArgumentNullException.ThrowIfNull(query);
CheckDisposed();
@@ -3059,12 +3057,12 @@ namespace Emby.Server.Implementations.Data
if (string.Equals(name, ItemSortBy.Artist, StringComparison.OrdinalIgnoreCase))
{
- return "(select CleanValue from itemvalues where ItemId=Guid and Type=0 LIMIT 1)";
+ return "(select CleanValue from ItemValues where ItemId=Guid and Type=0 LIMIT 1)";
}
if (string.Equals(name, ItemSortBy.AlbumArtist, StringComparison.OrdinalIgnoreCase))
{
- return "(select CleanValue from itemvalues where ItemId=Guid and Type=1 LIMIT 1)";
+ return "(select CleanValue from ItemValues where ItemId=Guid and Type=1 LIMIT 1)";
}
if (string.Equals(name, ItemSortBy.OfficialRating, StringComparison.OrdinalIgnoreCase))
@@ -3074,7 +3072,7 @@ namespace Emby.Server.Implementations.Data
if (string.Equals(name, ItemSortBy.Studio, StringComparison.OrdinalIgnoreCase))
{
- return "(select CleanValue from itemvalues where ItemId=Guid and Type=3 LIMIT 1)";
+ return "(select CleanValue from ItemValues where ItemId=Guid and Type=3 LIMIT 1)";
}
if (string.Equals(name, ItemSortBy.SeriesDatePlayed, StringComparison.OrdinalIgnoreCase))
@@ -3147,16 +3145,18 @@ namespace Emby.Server.Implementations.Data
return ItemSortBy.IndexNumber;
}
+ if (string.Equals(name, ItemSortBy.SimilarityScore, StringComparison.OrdinalIgnoreCase))
+ {
+ return ItemSortBy.SimilarityScore;
+ }
+
// Unknown SortBy, just sort by the SortName.
return ItemSortBy.SortName;
}
public List<Guid> GetItemIdsList(InternalItemsQuery query)
{
- if (query == null)
- {
- throw new ArgumentNullException(nameof(query));
- }
+ ArgumentNullException.ThrowIfNull(query);
CheckDisposed();
@@ -3847,7 +3847,7 @@ namespace Emby.Server.Implementations.Data
{
var paramName = "@ArtistIds" + index;
- clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
+ clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
if (statement != null)
{
statement.TryBind(paramName, artistId);
@@ -3868,7 +3868,7 @@ namespace Emby.Server.Implementations.Data
{
var paramName = "@ArtistIds" + index;
- clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))");
+ clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))");
if (statement != null)
{
statement.TryBind(paramName, artistId);
@@ -3889,7 +3889,7 @@ namespace Emby.Server.Implementations.Data
{
var paramName = "@ArtistIds" + index;
- clauses.Add("((select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from itemvalues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from itemvalues where ItemId=Guid and Type=1))");
+ clauses.Add("((select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from ItemValues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from ItemValues where ItemId=Guid and Type=1))");
if (statement != null)
{
statement.TryBind(paramName, artistId);
@@ -3931,7 +3931,7 @@ namespace Emby.Server.Implementations.Data
{
var paramName = "@ExcludeArtistId" + index;
- clauses.Add("(guid not in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
+ clauses.Add("(guid not in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
if (statement != null)
{
statement.TryBind(paramName, artistId);
@@ -3952,7 +3952,7 @@ namespace Emby.Server.Implementations.Data
{
var paramName = "@GenreId" + index;
- clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))");
+ clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))");
if (statement != null)
{
statement.TryBind(paramName, genreId);
@@ -3971,7 +3971,7 @@ namespace Emby.Server.Implementations.Data
var index = 0;
foreach (var item in query.Genres)
{
- clauses.Add("@Genre" + index + " in (select CleanValue from itemvalues where ItemId=Guid and Type=2)");
+ clauses.Add("@Genre" + index + " in (select CleanValue from ItemValues where ItemId=Guid and Type=2)");
if (statement != null)
{
statement.TryBind("@Genre" + index, GetCleanValue(item));
@@ -3990,7 +3990,7 @@ namespace Emby.Server.Implementations.Data
var index = 0;
foreach (var item in tags)
{
- clauses.Add("@Tag" + index + " in (select CleanValue from itemvalues where ItemId=Guid and Type=4)");
+ clauses.Add("@Tag" + index + " in (select CleanValue from ItemValues where ItemId=Guid and Type=4)");
if (statement != null)
{
statement.TryBind("@Tag" + index, GetCleanValue(item));
@@ -4009,7 +4009,7 @@ namespace Emby.Server.Implementations.Data
var index = 0;
foreach (var item in excludeTags)
{
- clauses.Add("@ExcludeTag" + index + " not in (select CleanValue from itemvalues where ItemId=Guid and Type=4)");
+ clauses.Add("@ExcludeTag" + index + " not in (select CleanValue from ItemValues where ItemId=Guid and Type=4)");
if (statement != null)
{
statement.TryBind("@ExcludeTag" + index, GetCleanValue(item));
@@ -4030,7 +4030,7 @@ namespace Emby.Server.Implementations.Data
{
var paramName = "@StudioId" + index;
- clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=3))");
+ clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=3))");
if (statement != null)
{
@@ -4509,7 +4509,7 @@ namespace Emby.Server.Implementations.Data
{
int index = 0;
string excludedTags = string.Join(',', query.ExcludeInheritedTags.Select(_ => paramName + index++));
- whereClauses.Add("((select CleanValue from itemvalues where ItemId=Guid and Type=6 and cleanvalue in (" + excludedTags + ")) is null)");
+ whereClauses.Add("((select CleanValue from ItemValues where ItemId=Guid and Type=6 and cleanvalue in (" + excludedTags + ")) is null)");
}
else
{
@@ -4744,11 +4744,11 @@ namespace Emby.Server.Implementations.Data
';',
new string[]
{
- "delete from itemvalues where type = 6",
+ "delete from ItemValues where type = 6",
- "insert into itemvalues (ItemId, Type, Value, CleanValue) select ItemId, 6, Value, CleanValue from ItemValues where Type=4",
+ "insert into ItemValues (ItemId, Type, Value, CleanValue) select ItemId, 6, Value, CleanValue from ItemValues where Type=4",
- @"insert into itemvalues (ItemId, Type, Value, CleanValue) select AncestorIds.itemid, 6, ItemValues.Value, ItemValues.CleanValue
+ @"insert into ItemValues (ItemId, Type, Value, CleanValue) select AncestorIds.itemid, 6, ItemValues.Value, ItemValues.CleanValue
FROM AncestorIds
LEFT JOIN ItemValues ON (AncestorIds.AncestorId = ItemValues.ItemId)
where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type = 4 "
@@ -4816,10 +4816,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
public List<string> GetPeopleNames(InternalPeopleQuery query)
{
- if (query == null)
- {
- throw new ArgumentNullException(nameof(query));
- }
+ ArgumentNullException.ThrowIfNull(query);
CheckDisposed();
@@ -4859,10 +4856,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
public List<PersonInfo> GetPeople(InternalPeopleQuery query)
{
- if (query == null)
- {
- throw new ArgumentNullException(nameof(query));
- }
+ ArgumentNullException.ThrowIfNull(query);
CheckDisposed();
@@ -4913,6 +4907,7 @@ SELECT key FROM UserDatas WHERE isFavorite=@IsFavorite AND userId=@UserId)
AND Type = @InternalPersonType)");
statement?.TryBind("@IsFavorite", query.IsFavorite.Value);
statement?.TryBind("@InternalPersonType", typeof(Person).FullName);
+ statement?.TryBind("@UserId", query.User.InternalId);
}
if (!query.ItemId.Equals(default))
@@ -4967,11 +4962,6 @@ AND Type = @InternalPersonType)");
statement?.TryBind("@NameContains", "%" + query.NameContains + "%");
}
- if (query.User != null)
- {
- statement?.TryBind("@UserId", query.User.InternalId);
- }
-
return whereClauses;
}
@@ -4982,10 +4972,7 @@ AND Type = @InternalPersonType)");
throw new ArgumentNullException(nameof(itemId));
}
- if (ancestorIds == null)
- {
- throw new ArgumentNullException(nameof(ancestorIds));
- }
+ ArgumentNullException.ThrowIfNull(ancestorIds);
CheckDisposed();
@@ -5158,10 +5145,7 @@ AND Type = @InternalPersonType)");
private QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType)
{
- if (query == null)
- {
- throw new ArgumentNullException(nameof(query));
- }
+ ArgumentNullException.ThrowIfNull(query);
if (!query.Limit.HasValue)
{
@@ -5514,10 +5498,7 @@ AND Type = @InternalPersonType)");
throw new ArgumentNullException(nameof(itemId));
}
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
+ ArgumentNullException.ThrowIfNull(values);
CheckDisposed();
@@ -5590,10 +5571,7 @@ AND Type = @InternalPersonType)");
throw new ArgumentNullException(nameof(itemId));
}
- if (people == null)
- {
- throw new ArgumentNullException(nameof(people));
- }
+ ArgumentNullException.ThrowIfNull(people);
CheckDisposed();
@@ -5693,10 +5671,7 @@ AND Type = @InternalPersonType)");
{
CheckDisposed();
- if (query == null)
- {
- throw new ArgumentNullException(nameof(query));
- }
+ ArgumentNullException.ThrowIfNull(query);
var cmdText = _mediaStreamSaveColumnsSelectQuery;
@@ -5749,10 +5724,7 @@ AND Type = @InternalPersonType)");
throw new ArgumentNullException(nameof(id));
}
- if (streams == null)
- {
- throw new ArgumentNullException(nameof(streams));
- }
+ ArgumentNullException.ThrowIfNull(streams);
cancellationToken.ThrowIfCancellationRequested();
@@ -5763,7 +5735,7 @@ AND Type = @InternalPersonType)");
{
var itemIdBlob = id.ToByteArray();
- // First delete chapters
+ // Delete existing mediastreams
db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob);
InsertMediaStreams(itemIdBlob, streams, db);
@@ -5855,6 +5827,15 @@ AND Type = @InternalPersonType)");
statement.TryBind("@ColorPrimaries" + index, stream.ColorPrimaries);
statement.TryBind("@ColorSpace" + index, stream.ColorSpace);
statement.TryBind("@ColorTransfer" + index, stream.ColorTransfer);
+
+ statement.TryBind("@DvVersionMajor" + index, stream.DvVersionMajor);
+ statement.TryBind("@DvVersionMinor" + index, stream.DvVersionMinor);
+ statement.TryBind("@DvProfile" + index, stream.DvProfile);
+ statement.TryBind("@DvLevel" + index, stream.DvLevel);
+ statement.TryBind("@RpuPresentFlag" + index, stream.RpuPresentFlag);
+ statement.TryBind("@ElPresentFlag" + index, stream.ElPresentFlag);
+ statement.TryBind("@BlPresentFlag" + index, stream.BlPresentFlag);
+ statement.TryBind("@DvBlSignalCompatibilityId" + index, stream.DvBlSignalCompatibilityId);
}
statement.Reset();
@@ -5867,10 +5848,10 @@ AND Type = @InternalPersonType)");
}
/// <summary>
- /// Gets the chapter.
+ /// Gets the media stream.
/// </summary>
/// <param name="reader">The reader.</param>
- /// <returns>ChapterInfo.</returns>
+ /// <returns>MediaStream.</returns>
private MediaStream GetMediaStream(IReadOnlyList<ResultSetValue> reader)
{
var item = new MediaStream
@@ -6026,6 +6007,46 @@ AND Type = @InternalPersonType)");
item.ColorTransfer = colorTransfer;
}
+ if (reader.TryGetInt32(35, out var dvVersionMajor))
+ {
+ item.DvVersionMajor = dvVersionMajor;
+ }
+
+ if (reader.TryGetInt32(36, out var dvVersionMinor))
+ {
+ item.DvVersionMinor = dvVersionMinor;
+ }
+
+ if (reader.TryGetInt32(37, out var dvProfile))
+ {
+ item.DvProfile = dvProfile;
+ }
+
+ if (reader.TryGetInt32(38, out var dvLevel))
+ {
+ item.DvLevel = dvLevel;
+ }
+
+ if (reader.TryGetInt32(39, out var rpuPresentFlag))
+ {
+ item.RpuPresentFlag = rpuPresentFlag;
+ }
+
+ if (reader.TryGetInt32(40, out var elPresentFlag))
+ {
+ item.ElPresentFlag = elPresentFlag;
+ }
+
+ if (reader.TryGetInt32(41, out var blPresentFlag))
+ {
+ item.BlPresentFlag = blPresentFlag;
+ }
+
+ if (reader.TryGetInt32(42, out var dvBlSignalCompatibilityId))
+ {
+ item.DvBlSignalCompatibilityId = dvBlSignalCompatibilityId;
+ }
+
if (item.Type == MediaStreamType.Subtitle)
{
item.LocalizedUndefined = _localization.GetLocalizedString("Undefined");
@@ -6041,10 +6062,7 @@ AND Type = @InternalPersonType)");
{
CheckDisposed();
- if (query == null)
- {
- throw new ArgumentNullException(nameof(query));
- }
+ ArgumentNullException.ThrowIfNull(query);
var cmdText = _mediaAttachmentSaveColumnsSelectQuery;
@@ -6086,10 +6104,7 @@ AND Type = @InternalPersonType)");
throw new ArgumentException("Guid can't be empty.", nameof(id));
}
- if (attachments == null)
- {
- throw new ArgumentNullException(nameof(attachments));
- }
+ ArgumentNullException.ThrowIfNull(attachments);
cancellationToken.ThrowIfCancellationRequested();
diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
index ba86dc156..8d78d644d 100644
--- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
@@ -133,10 +133,7 @@ namespace Emby.Server.Implementations.Data
/// <inheritdoc />
public void SaveUserData(long userId, string key, UserItemData userData, CancellationToken cancellationToken)
{
- if (userData == null)
- {
- throw new ArgumentNullException(nameof(userData));
- }
+ ArgumentNullException.ThrowIfNull(userData);
if (userId <= 0)
{
@@ -154,10 +151,7 @@ namespace Emby.Server.Implementations.Data
/// <inheritdoc />
public void SaveAllUserData(long userId, UserItemData[] userData, CancellationToken cancellationToken)
{
- if (userData == null)
- {
- throw new ArgumentNullException(nameof(userData));
- }
+ ArgumentNullException.ThrowIfNull(userData);
if (userId <= 0)
{
@@ -304,10 +298,7 @@ namespace Emby.Server.Implementations.Data
public UserItemData GetUserData(long userId, List<string> keys)
{
- if (keys == null)
- {
- throw new ArgumentNullException(nameof(keys));
- }
+ ArgumentNullException.ThrowIfNull(keys);
if (keys.Count == 0)
{
diff --git a/Emby.Server.Implementations/Data/SynchronouseMode.cs b/Emby.Server.Implementations/Data/SynchronousMode.cs
index cde524e2e..cde524e2e 100644
--- a/Emby.Server.Implementations/Data/SynchronouseMode.cs
+++ b/Emby.Server.Implementations/Data/SynchronousMode.cs
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index efcfccafe..3d2b8f7f6 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -182,7 +182,7 @@ namespace Emby.Server.Implementations.Dto
if (options.ContainsField(ItemFields.People))
{
- AttachPeople(dto, item);
+ AttachPeople(dto, item, user);
}
if (options.ContainsField(ItemFields.PrimaryImageAspectRatio))
@@ -503,7 +503,8 @@ namespace Emby.Server.Implementations.Dto
/// </summary>
/// <param name="dto">The dto.</param>
/// <param name="item">The item.</param>
- private void AttachPeople(BaseItemDto dto, BaseItem item)
+ /// <param name="user">The requesting user.</param>
+ private void AttachPeople(BaseItemDto dto, BaseItem item, User user = null)
{
// Ordering by person type to ensure actors and artists are at the front.
// This is taking advantage of the fact that they both begin with A
@@ -560,6 +561,9 @@ namespace Emby.Server.Implementations.Dto
return null;
}
}).Where(i => i != null)
+ .Where(i => user == null ?
+ true :
+ i.IsVisible(user))
.GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
.Select(x => x.First())
.ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase);
@@ -1094,7 +1098,7 @@ namespace Emby.Server.Implementations.Dto
{
if (item is IHasTrailers hasTrailers)
{
- dto.LocalTrailerCount = hasTrailers.GetTrailerCount();
+ dto.LocalTrailerCount = hasTrailers.LocalTrailers.Count;
}
else
{
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 4d43e89c8..2792a4c7c 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -24,15 +24,15 @@
<ItemGroup>
<PackageReference Include="DiscUtils.Udf" Version="0.16.13" />
- <PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" />
+ <PackageReference Include="Jellyfin.XmlTv" Version="10.8.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.3" />
- <PackageReference Include="Mono.Nat" Version="3.0.2" />
- <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.3" />
- <PackageReference Include="sharpcompress" Version="0.30.1" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.9" />
+ <PackageReference Include="Mono.Nat" Version="3.0.3" />
+ <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.4" />
+ <PackageReference Include="sharpcompress" Version="0.32.2" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
<PackageReference Include="DotNet.Glob" Version="3.1.3" />
</ItemGroup>
@@ -60,7 +60,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
deleted file mode 100644
index 15ab363fe..000000000
--- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
+++ /dev/null
@@ -1,60 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Threading.Tasks;
-using Jellyfin.Data.Entities;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Session;
-using Microsoft.AspNetCore.Http;
-
-namespace Emby.Server.Implementations.HttpServer.Security
-{
- public class SessionContext : ISessionContext
- {
- private readonly IUserManager _userManager;
- private readonly ISessionManager _sessionManager;
- private readonly IAuthorizationContext _authContext;
-
- public SessionContext(IUserManager userManager, IAuthorizationContext authContext, ISessionManager sessionManager)
- {
- _userManager = userManager;
- _authContext = authContext;
- _sessionManager = sessionManager;
- }
-
- public async Task<SessionInfo> GetSession(HttpContext requestContext)
- {
- var authorization = await _authContext.GetAuthorizationInfo(requestContext).ConfigureAwait(false);
-
- var user = authorization.User;
- return await _sessionManager.LogSessionActivity(
- authorization.Client,
- authorization.Version,
- authorization.DeviceId,
- authorization.Device,
- requestContext.GetNormalizedRemoteIp().ToString(),
- user).ConfigureAwait(false);
- }
-
- public Task<SessionInfo> GetSession(object requestContext)
- {
- return GetSession((HttpContext)requestContext);
- }
-
- public async Task<User?> GetUser(HttpContext requestContext)
- {
- var session = await GetSession(requestContext).ConfigureAwait(false);
-
- return session.UserId.Equals(default)
- ? null
- : _userManager.GetUserById(session.UserId);
- }
-
- public Task<User?> GetUser(object requestContext)
- {
- return GetUser(((HttpRequest)requestContext).HttpContext);
- }
- }
-}
diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
index b87f1bc22..d095248fa 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
@@ -11,7 +11,6 @@ using Jellyfin.Extensions.Json;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Session;
-using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.HttpServer
@@ -19,7 +18,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary>
/// Class WebSocketConnection.
/// </summary>
- public class WebSocketConnection : IWebSocketConnection, IDisposable
+ public class WebSocketConnection : IWebSocketConnection
{
/// <summary>
/// The logger.
@@ -36,6 +35,8 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
private readonly WebSocket _socket;
+ private bool _disposed = false;
+
/// <summary>
/// Initializes a new instance of the <see cref="WebSocketConnection" /> class.
/// </summary>
@@ -244,10 +245,39 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
+ if (_disposed)
+ {
+ return;
+ }
+
if (dispose)
{
_socket.Dispose();
}
+
+ _disposed = true;
+ }
+
+ /// <inheritdoc />
+ public async ValueTask DisposeAsync()
+ {
+ await DisposeAsyncCore().ConfigureAwait(false);
+ Dispose(false);
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// Used to perform asynchronous cleanup of managed resources or for cascading calls to <see cref="DisposeAsync"/>.
+ /// </summary>
+ /// <returns>A ValueTask.</returns>
+ protected virtual async ValueTask DisposeAsyncCore()
+ {
+ if (_socket.State == WebSocketState.Open)
+ {
+ await _socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "System Shutdown", CancellationToken.None).ConfigureAwait(false);
+ }
+
+ _socket.Dispose();
}
}
}
diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
index 399ece7fd..120b1812a 100644
--- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
@@ -262,6 +262,10 @@ namespace Emby.Server.Implementations.IO
_logger.LogError(ex, "Reading the file size of the symlink at {Path} failed. Marking the file as not existing.", fileInfo.FullName);
result.Exists = false;
}
+ catch (UnauthorizedAccessException ex)
+ {
+ _logger.LogError(ex, "Reading the file at {Path} failed due to a permissions exception.", fileInfo.FullName);
+ }
}
}
diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs
index 9f9a4902a..0faa0f8fa 100644
--- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs
+++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs
@@ -93,6 +93,7 @@ namespace Emby.Server.Implementations.Images
returnItems.Shuffle();
return returnItems;
}
+
returnItems = items
.Where(i => i.HasImage(ImageType.Primary))
.ToList();
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index a9428ae9b..ee94670eb 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -46,7 +46,6 @@ using MediaBrowser.Model.Library;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Caching.Memory;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using EpisodeInfo = Emby.Naming.TV.EpisodeInfo;
@@ -282,10 +281,7 @@ namespace Emby.Server.Implementations.Library
public void RegisterItem(BaseItem item)
{
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
+ ArgumentNullException.ThrowIfNull(item);
if (item is IItemByName)
{
@@ -312,10 +308,7 @@ namespace Emby.Server.Implementations.Library
public void DeleteItem(BaseItem item, DeleteOptions options, bool notifyParentItem)
{
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
+ ArgumentNullException.ThrowIfNull(item);
var parent = item.GetOwner() ?? item.GetParent();
@@ -324,10 +317,7 @@ namespace Emby.Server.Implementations.Library
public void DeleteItem(BaseItem item, DeleteOptions options, BaseItem parent, bool notifyParentItem)
{
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
+ ArgumentNullException.ThrowIfNull(item);
if (item.SourceType == SourceType.Channel)
{
@@ -510,10 +500,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(key));
}
- if (type == null)
- {
- throw new ArgumentNullException(nameof(type));
- }
+ ArgumentNullException.ThrowIfNull(type);
string programDataPath = _configurationManager.ApplicationPaths.ProgramDataPath;
if (key.StartsWith(programDataPath, StringComparison.Ordinal))
@@ -545,10 +532,7 @@ namespace Emby.Server.Implementations.Library
string collectionType = null,
LibraryOptions libraryOptions = null)
{
- if (fileInfo == null)
- {
- throw new ArgumentNullException(nameof(fileInfo));
- }
+ ArgumentNullException.ThrowIfNull(fileInfo);
var fullPath = fileInfo.FullName;
@@ -1855,12 +1839,11 @@ namespace Emby.Server.Implementations.Library
/// <inheritdoc />
public async Task UpdateImagesAsync(BaseItem item, bool forceUpdate = false)
{
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
+ ArgumentNullException.ThrowIfNull(item);
- var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
+ var outdated = forceUpdate
+ ? item.ImageInfos.Where(i => i.Path != null).ToArray()
+ : item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
// Skip image processing if current or live tv source
if (outdated.Length == 0 || item.SourceType != SourceType.Library)
{
@@ -1883,7 +1866,7 @@ namespace Emby.Server.Implementations.Library
_logger.LogWarning("Cannot get image index for {ImagePath}", img.Path);
continue;
}
- catch (Exception ex) when (ex is InvalidOperationException || ex is IOException)
+ catch (Exception ex) when (ex is InvalidOperationException or IOException)
{
_logger.LogWarning(ex, "Cannot fetch image from {ImagePath}", img.Path);
continue;
@@ -1895,23 +1878,24 @@ namespace Emby.Server.Implementations.Library
}
}
+ ImageDimensions size;
try
{
- ImageDimensions size = _imageProcessor.GetImageDimensions(item, image);
+ size = _imageProcessor.GetImageDimensions(item, image);
image.Width = size.Width;
image.Height = size.Height;
}
catch (Exception ex)
{
_logger.LogError(ex, "Cannot get image dimensions for {ImagePath}", image.Path);
+ size = new ImageDimensions(0, 0);
image.Width = 0;
image.Height = 0;
- continue;
}
try
{
- image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path);
+ image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path, size);
}
catch (Exception ex)
{
@@ -2294,10 +2278,7 @@ namespace Emby.Server.Implementations.Library
string viewType,
string sortName)
{
- if (parent == null)
- {
- throw new ArgumentNullException(nameof(parent));
- }
+ ArgumentNullException.ThrowIfNull(parent);
var name = parent.Name;
var parentId = parent.Id;
@@ -2451,6 +2432,12 @@ namespace Emby.Server.Implementations.Library
}
/// <inheritdoc />
+ public void QueueLibraryScan()
+ {
+ _taskManager.QueueScheduledTask<RefreshMediaLibraryTask>();
+ }
+
+ /// <inheritdoc />
public int? GetSeasonNumberFromPath(string path)
=> SeasonPathParser.Parse(path, true, true).SeasonNumber;
@@ -2520,7 +2507,7 @@ namespace Emby.Server.Implementations.Library
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error reading the episode informations with ffprobe. Episode: {EpisodeInfo}", episodeInfo.Path);
+ _logger.LogError(ex, "Error reading the episode information with ffprobe. Episode: {EpisodeInfo}", episodeInfo.Path);
}
var changed = false;
@@ -2757,7 +2744,8 @@ namespace Emby.Server.Implementations.Library
public List<Person> GetPeopleItems(InternalPeopleQuery query)
{
- return _itemRepository.GetPeopleNames(query).Select(i =>
+ return _itemRepository.GetPeopleNames(query)
+ .Select(i =>
{
try
{
@@ -2768,7 +2756,12 @@ namespace Emby.Server.Implementations.Library
_logger.LogError(ex, "Error getting person");
return null;
}
- }).Where(i => i != null).ToList();
+ })
+ .Where(i => i != null)
+ .Where(i => query.User == null ?
+ true :
+ i.IsVisible(query.User))
+ .ToList();
}
public List<string> GetPeopleNames(InternalPeopleQuery query)
@@ -2840,10 +2833,12 @@ namespace Emby.Server.Implementations.Library
var existingNameCount = 1; // first numbered name will be 2
var virtualFolderPath = Path.Combine(rootFolderPath, name);
+ var originalName = name;
while (Directory.Exists(virtualFolderPath))
{
existingNameCount++;
- virtualFolderPath = Path.Combine(rootFolderPath, name + " " + existingNameCount);
+ name = originalName + existingNameCount;
+ virtualFolderPath = Path.Combine(rootFolderPath, name);
}
var mediaPathInfos = options.PathInfos;
@@ -2967,10 +2962,7 @@ namespace Emby.Server.Implementations.Library
private void AddMediaPathInternal(string virtualFolderName, MediaPathInfo pathInfo, bool saveLibraryOptions)
{
- if (pathInfo == null)
- {
- throw new ArgumentNullException(nameof(pathInfo));
- }
+ ArgumentNullException.ThrowIfNull(pathInfo);
var path = pathInfo.Path;
@@ -3017,10 +3009,7 @@ namespace Emby.Server.Implementations.Library
public void UpdateMediaPath(string virtualFolderName, MediaPathInfo mediaPath)
{
- if (mediaPath == null)
- {
- throw new ArgumentNullException(nameof(mediaPath));
- }
+ ArgumentNullException.ThrowIfNull(mediaPath);
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index d9a1a5487..bfccc7db7 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -151,7 +151,11 @@ namespace Emby.Server.Implementations.Library
{
var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
- if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio || i.Type == MediaStreamType.Video))
+ // If file is strm or main media stream is missing, force a metadata refresh with remote probing
+ if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder
+ && (item.Path.EndsWith(".strm", StringComparison.OrdinalIgnoreCase)
+ || (item.MediaType == MediaType.Video && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Video))
+ || (item.MediaType == MediaType.Audio && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio))))
{
await item.RefreshMetadata(
new MetadataRefreshOptions(_directoryService)
@@ -318,10 +322,7 @@ namespace Emby.Server.Implementations.Library
public List<MediaSourceInfo> GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, User user = null)
{
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
+ ArgumentNullException.ThrowIfNull(item);
var hasMediaSources = (IHasMediaSources)item;
diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs
index c5abb9a0a..20a2edb05 100644
--- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs
+++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs
@@ -13,11 +13,11 @@ namespace Emby.Server.Implementations.Library
{
public static int? GetDefaultAudioStreamIndex(IReadOnlyList<MediaStream> streams, IReadOnlyList<string> preferredLanguages, bool preferDefaultTrack)
{
- var sortedStreams = GetSortedStreams(streams, MediaStreamType.Audio, preferredLanguages);
+ var sortedStreams = GetSortedStreams(streams, MediaStreamType.Audio, preferredLanguages).ToList();
if (preferDefaultTrack)
{
- var defaultStream = streams.FirstOrDefault(i => i.IsDefault);
+ var defaultStream = sortedStreams.FirstOrDefault(i => i.IsDefault);
if (defaultStream != null)
{
diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 140c4272e..b2f388a66 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -225,7 +225,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
- return ResolveVideos<Episode>(parent, files, true, collectionType, true);
+ return ResolveVideos<Episode>(parent, files, false, collectionType, true);
}
return null;
@@ -387,7 +387,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (!string.IsNullOrEmpty(item.Path))
{
- // check for imdb id - we use full media path, as we can assume, that this will match in any use case (wither id in parent dir or in file name)
+ // check for imdb id - we use full media path, as we can assume, that this will match in any use case (either id in parent dir or in file name)
var imdbid = item.Path.AsSpan().GetAttributeValue("imdbid");
if (!string.IsNullOrWhiteSpace(imdbid))
@@ -464,7 +464,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
var result = ResolveVideos<T>(parent, fileSystemEntries, SupportsMultiVersion, collectionType, parseName) ??
new MultiItemResolverResult();
- if (result.Items.Count == 1)
+ var isPhotosCollection = string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)
+ || string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase);
+ if (!isPhotosCollection && result.Items.Count == 1)
{
var videoPath = result.Items[0].Path;
var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(videoPath, i.Name));
diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
index bc2915db6..af4abfb80 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
@@ -91,10 +91,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
internal static bool IsImageFile(string path, IImageProcessor imageProcessor)
{
- if (path == null)
- {
- throw new ArgumentNullException(nameof(path));
- }
+ ArgumentNullException.ThrowIfNull(path);
var filename = Path.GetFileNameWithoutExtension(path);
diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs
index 96702d152..60778a443 100644
--- a/Emby.Server.Implementations/Library/SearchEngine.cs
+++ b/Emby.Server.Implementations/Library/SearchEngine.cs
@@ -5,9 +5,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using Diacritics.Extensions;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs
index 3810a76c4..aecab7d9c 100644
--- a/Emby.Server.Implementations/Library/UserDataManager.cs
+++ b/Emby.Server.Implementations/Library/UserDataManager.cs
@@ -53,15 +53,9 @@ namespace Emby.Server.Implementations.Library
public void SaveUserData(User user, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken)
{
- if (userData == null)
- {
- throw new ArgumentNullException(nameof(userData));
- }
+ ArgumentNullException.ThrowIfNull(userData);
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
+ ArgumentNullException.ThrowIfNull(item);
cancellationToken.ThrowIfCancellationRequested();
@@ -194,10 +188,7 @@ namespace Emby.Server.Implementations.Library
/// <exception cref="ArgumentNullException"><paramref name="data"/> is <c>null</c>.</exception>
private UserItemDataDto GetUserItemDataDto(UserItemData data)
{
- if (data == null)
- {
- throw new ArgumentNullException(nameof(data));
- }
+ ArgumentNullException.ThrowIfNull(data);
return new UserItemDataDto
{
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 2753cf177..4da677636 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -995,7 +995,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- throw new Exception("Tuner not found.");
+ throw new ResourceNotFoundException("Tuner not found.");
}
public async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken)
@@ -1223,10 +1223,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, ActiveRecordingInfo activeRecordingInfo)
{
- if (timer == null)
- {
- throw new ArgumentNullException(nameof(timer));
- }
+ ArgumentNullException.ThrowIfNull(timer);
LiveTvProgram programInfo = null;
@@ -2347,10 +2344,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private IEnumerable<TimerInfo> GetTimersForSeries(SeriesTimerInfo seriesTimer)
{
- if (seriesTimer == null)
- {
- throw new ArgumentNullException(nameof(seriesTimer));
- }
+ ArgumentNullException.ThrowIfNull(seriesTimer);
var query = new InternalItemsQuery
{
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index 582e61d79..08534de59 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -13,6 +13,7 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
+using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
@@ -65,7 +66,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
await RecordFromFile(mediaSource, mediaSource.Path, targetFile, onStarted, cancellationTokenSource.Token).ConfigureAwait(false);
- _logger.LogInformation("Recording completed to file {0}", targetFile);
+ _logger.LogInformation("Recording completed to file {Path}", targetFile);
}
private async Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, Action onStarted, CancellationToken cancellationToken)
@@ -115,7 +116,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
_ = StartStreamingLog(_process.StandardError.BaseStream, _logFileStream);
- _logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
+ _logger.LogInformation("ffmpeg recording process started for {Path}", _targetPath);
+
+ // Block until ffmpeg exits
+ await _taskCompletionSource.Task.ConfigureAwait(false);
}
private string GetCommandLineArgs(MediaSourceInfo mediaSource, string inputTempFile, string targetFile)
@@ -294,7 +298,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
else
{
_taskCompletionSource.TrySetException(
- new Exception(
+ new FfmpegException(
string.Format(
CultureInfo.InvariantCulture,
"Recording for {0} failed. Exit code {1}",
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
index 46979bfc5..58b798ce6 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
@@ -84,10 +84,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public virtual void Update(T item)
{
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
+ ArgumentNullException.ThrowIfNull(item);
lock (_fileDataLock)
{
@@ -107,10 +104,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public virtual void Add(T item)
{
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
+ ArgumentNullException.ThrowIfNull(item);
lock (_fileDataLock)
{
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
index 32245f899..40dcca94f 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
@@ -2,6 +2,7 @@
using System;
using System.Globalization;
+using System.Text;
using MediaBrowser.Controller.LiveTv;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
@@ -48,12 +49,18 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (!string.IsNullOrWhiteSpace(info.EpisodeTitle))
{
+ var tmpName = name;
if (addHyphen)
{
- name += " -";
+ tmpName += " -";
}
- name += " " + info.EpisodeTitle;
+ tmpName += " " + info.EpisodeTitle;
+ // Since the filename will be used with file ext. (.mp4, .ts, etc)
+ if (Encoding.UTF8.GetByteCount(tmpName) < 250)
+ {
+ name = tmpName;
+ }
}
}
else if (info.IsMovie && info.ProductionYear != null)
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index ffa0d9b6a..4311db28d 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -20,6 +20,7 @@ using Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@@ -591,13 +592,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
catch (HttpRequestException ex)
{
- if (ex.StatusCode.HasValue)
+ if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.BadRequest)
{
- if ((int)ex.StatusCode.Value == 400)
- {
- _tokens.Clear();
- _lastErrorResponse = DateTime.UtcNow;
- }
+ _tokens.Clear();
+ _lastErrorResponse = DateTime.UtcNow;
}
throw;
@@ -662,7 +660,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return root.Token;
}
- throw new Exception("Could not authenticate with Schedules Direct Error: " + root.Message);
+ throw new AuthenticationException("Could not authenticate with Schedules Direct Error: " + root.Message);
}
private async Task AddLineupToAccount(ListingsProviderInfo info, CancellationToken cancellationToken)
@@ -697,7 +695,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
if (string.IsNullOrEmpty(token))
{
- throw new Exception("token required");
+ throw new ArgumentException("token required");
}
_logger.LogInformation("Headends on account ");
@@ -768,14 +766,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings
var listingsId = info.ListingsId;
if (string.IsNullOrEmpty(listingsId))
{
- throw new Exception("ListingsId required");
+ throw new ArgumentException("ListingsId required");
}
var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
if (string.IsNullOrEmpty(token))
{
- throw new Exception("token required");
+ throw new ArgumentException("token required");
}
using var options = new HttpRequestMessage(HttpMethod.Get, ApiUrl + "/lineups/" + listingsId);
diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
index 3da9d02b8..bd1cd1e1d 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
@@ -8,6 +8,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
+using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Extensions;
@@ -26,6 +27,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
public class XmlTvListingsProvider : IListingsProvider
{
+ private static readonly TimeSpan _maxCacheAge = TimeSpan.FromHours(1);
+
private readonly IServerConfigurationManager _config;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger<XmlTvListingsProvider> _logger;
@@ -69,13 +72,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return UnzipIfNeeded(info.Path, info.Path);
}
- string cacheFilename = DateTime.UtcNow.DayOfYear.ToString(CultureInfo.InvariantCulture) + "-" + DateTime.UtcNow.Hour.ToString(CultureInfo.InvariantCulture) + "-" + info.Id + ".xml";
+ string cacheFilename = info.Id + ".xml";
string cacheFile = Path.Combine(_config.ApplicationPaths.CachePath, "xmltv", cacheFilename);
- if (File.Exists(cacheFile))
+ if (File.Exists(cacheFile) && File.GetLastWriteTimeUtc(cacheFile) >= DateTime.UtcNow.Subtract(_maxCacheAge))
{
return UnzipIfNeeded(info.Path, cacheFile);
}
+ // Must check if file exists as parent directory may not exist.
+ if (File.Exists(cacheFile))
+ {
+ File.Delete(cacheFile);
+ }
+
_logger.LogInformation("Downloading xmltv listings from {Path}", info.Path);
Directory.CreateDirectory(Path.GetDirectoryName(cacheFile));
@@ -124,7 +133,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
using (var stream = File.OpenRead(file))
{
- string tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString());
+ string tempFolder = GetTempFolderPath(stream);
Directory.CreateDirectory(tempFolder);
_zipClient.ExtractFirstFileFromGz(stream, tempFolder, "data.xml");
@@ -137,7 +146,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
using (var stream = File.OpenRead(file))
{
- string tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString());
+ string tempFolder = GetTempFolderPath(stream);
Directory.CreateDirectory(tempFolder);
_zipClient.ExtractAllFromGz(stream, tempFolder, true);
@@ -146,6 +155,16 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
}
+ private string GetTempFolderPath(Stream stream)
+ {
+#pragma warning disable CA5351
+ using var md5 = MD5.Create();
+#pragma warning restore CA5351
+ var checksum = Convert.ToHexString(md5.ComputeHash(stream));
+ stream.Position = 0;
+ return Path.Combine(_config.ApplicationPaths.TempDirectory, checksum);
+ }
+
private string FindXmlFile(string directory)
{
return _fileSystem.GetFiles(directory, true)
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index 2a468e14d..bcb42e162 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -196,7 +196,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
IsInfiniteStream = true,
IsRemote = isRemote,
- IgnoreDts = true,
+ IgnoreDts = info.IgnoreDts,
SupportsDirectPlay = supportsDirectPlay,
SupportsDirectStream = supportsDirectStream,
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
index 708ff52d7..a423ec8f4 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
@@ -44,10 +44,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public async Task<Stream> GetListingsStream(TunerHostInfo info, CancellationToken cancellationToken)
{
- if (info == null)
- {
- throw new ArgumentNullException(nameof(info));
- }
+ ArgumentNullException.ThrowIfNull(info);
if (!info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
@@ -199,7 +196,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (string.IsNullOrWhiteSpace(numberString))
{
// Using this as a fallback now as this leads to Problems with channels like "5 USA"
- // where 5 isn't ment to be the channel number
+ // where 5 isn't meant to be the channel number
// Check for channel number with the format from SatIp
// #EXTINF:0,84. VOX Schweiz
// #EXTINF:0,84.0 - VOX Schweiz
diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json
index 18f17dda9..f356c98a9 100644
--- a/Emby.Server.Implementations/Localization/Core/af.json
+++ b/Emby.Server.Implementations/Localization/Core/af.json
@@ -119,5 +119,8 @@
"TaskCleanActivityLogDescription": "Verwyder aktiwiteitsaantekeninge ouer as die opgestelde ouderdom.",
"TaskCleanActivityLog": "Maak Aktiwiteitsaantekeninge Skoon",
"TaskOptimizeDatabaseDescription": "Komprimeer databasis en verkort vrye ruimte. As hierdie taak uitgevoer word nadat die media versameling geskandeer is of ander veranderings aangebring is wat databasisaanpassings impliseer, kan dit die prestasie verbeter.",
- "TaskOptimizeDatabase": "Optimaliseer databasis"
+ "TaskOptimizeDatabase": "Optimaliseer databasis",
+ "TaskKeyframeExtractorDescription": "Haal keyframes vanuit video lêers om meer presiese HLS afspeellyste te maak. Dit kan lank duur.",
+ "TaskKeyframeExtractor": "Keyframe Ekstraktor",
+ "External": "Ekstern"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json
index 9d4d40e51..9dc2fe799 100644
--- a/Emby.Server.Implementations/Localization/Core/ar.json
+++ b/Emby.Server.Implementations/Localization/Core/ar.json
@@ -1,9 +1,9 @@
{
- "Albums": "ألبومات",
+ "Albums": "البومات",
"AppDeviceValues": "تطبيق: {0}, جهاز: {1}",
"Application": "تطبيق",
"Artists": "الفنانين",
- "AuthenticationSucceededWithUserName": "{0} سجل الدخول بنجاح",
+ "AuthenticationSucceededWithUserName": "تمت مصادقة {0} بنجاح",
"Books": "الكتب",
"CameraImageUploadedFrom": "صورة كاميرا جديدة تم رفعها من {0}",
"Channels": "القنوات",
@@ -14,7 +14,7 @@
"FailedLoginAttemptWithUserName": "محاولة تسجيل الدخول فشلت من {0}",
"Favorites": "مفضلات",
"Folders": "المجلدات",
- "Genres": "التضنيفات",
+ "Genres": "التصنيفات",
"HeaderAlbumArtists": "فناني الألبوم",
"HeaderContinueWatching": "استمر بالمشاهدة",
"HeaderFavoriteAlbums": "الألبومات المفضلة",
@@ -30,8 +30,8 @@
"ItemAddedWithName": "تم إضافة {0} للمكتبة",
"ItemRemovedWithName": "تم إزالة {0} من المكتبة",
"LabelIpAddressValue": "عنوان الآي بي: {0}",
- "LabelRunningTimeValue": "المدة: {0}",
- "Latest": "الأحدث",
+ "LabelRunningTimeValue": "مدة التشغيل: {0}",
+ "Latest": "أحدث",
"MessageApplicationUpdated": "لقد تم تحديث خادم Jellyfin",
"MessageApplicationUpdatedTo": "تم تحديث خادم Jellyfin الى {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "تم تحديث إعدادات الخادم في قسم {0}",
@@ -40,7 +40,7 @@
"Movies": "الأفلام",
"Music": "الموسيقى",
"MusicVideos": "الفيديوهات الموسيقية",
- "NameInstallFailed": "فشل التثبيت {0}",
+ "NameInstallFailed": "فشل تثبيت {0}",
"NameSeasonNumber": "الموسم {0}",
"NameSeasonUnknown": "الموسم غير معروف",
"NewVersionIsAvailable": "نسخة جديدة من خادم Jellyfin متوفرة للتحميل.",
@@ -49,9 +49,9 @@
"NotificationOptionAudioPlayback": "بدأ تشغيل المقطع الصوتي",
"NotificationOptionAudioPlaybackStopped": "تم إيقاف تشغيل المقطع الصوتي",
"NotificationOptionCameraImageUploaded": "تم رفع صورة الكاميرا",
- "NotificationOptionInstallationFailed": "فشل التثبيت",
+ "NotificationOptionInstallationFailed": "فشل في التثبيت",
"NotificationOptionNewLibraryContent": "تم إضافة محتوى جديد",
- "NotificationOptionPluginError": "فشل في البرنامج المضاف",
+ "NotificationOptionPluginError": "فشل في الملحق",
"NotificationOptionPluginInstalled": "تم تثبيت الملحق",
"NotificationOptionPluginUninstalled": "تمت إزالة الملحق",
"NotificationOptionPluginUpdateInstalled": "تم تثبيت تحديثات الملحق",
@@ -67,22 +67,22 @@
"PluginUninstalledWithName": "تمت إزالة {0}",
"PluginUpdatedWithName": "تم تحديث {0}",
"ProviderValue": "المزود: {0}",
- "ScheduledTaskFailedWithName": "العملية {0} فشلت",
- "ScheduledTaskStartedWithName": "تم بدء {0}",
- "ServerNameNeedsToBeRestarted": "يحتاج لإعادة تشغيله {0}",
- "Shows": "الحلقات",
+ "ScheduledTaskFailedWithName": "فشلت العملية {0}",
+ "ScheduledTaskStartedWithName": "تم بدء العملية {0}",
+ "ServerNameNeedsToBeRestarted": "يحتاج {0} لإعادة التشغيل",
+ "Shows": "العروض",
"Songs": "الأغاني",
- "StartupEmbyServerIsLoading": "خادم Jellyfin قيد التشغيل . الرجاء المحاولة بعد قليل.",
+ "StartupEmbyServerIsLoading": "يتم تحميل خادم Jellyfin . الرجاء المحاولة بعد قليل.",
"SubtitleDownloadFailureForItem": "عملية إنزال الترجمة فشلت لـ{0}",
- "SubtitleDownloadFailureFromForItem": "الترجمات فشلت في التحميل من {0} الى {1}",
+ "SubtitleDownloadFailureFromForItem": "فشل تحميل الترجمات من {0} ل {1}",
"Sync": "مزامنة",
"System": "النظام",
"TvShows": "البرامج التلفزيونية",
"User": "المستخدم",
"UserCreatedWithName": "تم إنشاء المستخدم {0}",
"UserDeletedWithName": "تم حذف المستخدم {0}",
- "UserDownloadingItemWithValues": "{0} يقوم بإنزال {1}",
- "UserLockedOutWithName": "المستخدم {0} تم منعه من الدخول",
+ "UserDownloadingItemWithValues": "يقوم {0} بتنزيل {1}",
+ "UserLockedOutWithName": "تم منع المستخدم {0} من الدخول",
"UserOfflineFromDevice": "تم قطع اتصال {0} من {1}",
"UserOnlineFromDevice": "{0} متصل عبر {1}",
"UserPasswordChangedWithName": "تم تغيير كلمة السر للمستخدم {0}",
@@ -90,35 +90,38 @@
"UserStartedPlayingItemWithValues": "قام {0} ببدء تشغيل {1} على {2}",
"UserStoppedPlayingItemWithValues": "قام {0} بإيقاف تشغيل {1} على {2}",
"ValueHasBeenAddedToLibrary": "تمت اضافت {0} إلى مكتبة الوسائط",
- "ValueSpecialEpisodeName": "خاص - {0}",
+ "ValueSpecialEpisodeName": "حلقه خاصه - {0}",
"VersionNumber": "النسخة {0}",
- "TaskCleanCacheDescription": "يحذف ملفات ذاكرة التخزين المؤقت التي لم يعد النظام بحاجة إليها.",
- "TaskCleanCache": "احذف مجلد ذاكرة التخزين المؤقت",
+ "TaskCleanCacheDescription": "يحذف الملفات المؤقتة التي لم يعد النظام بحاجة إليها.",
+ "TaskCleanCache": "احذف ما بمجلد الملفات المؤقتة",
"TasksChannelsCategory": "قنوات الإنترنت",
"TasksLibraryCategory": "مكتبة",
"TasksMaintenanceCategory": "صيانة",
- "TaskRefreshLibraryDescription": "يقوم بفصح مكتبة الوسائط الخاصة بك بحثًا عن ملفات جديدة وتحديث البيانات الوصفية.",
+ "TaskRefreshLibraryDescription": "يفصح مكتبة الوسائط الخاصة بك بحثًا عن ملفات جديدة، ومن ثم يتحدث البيانات الوصفية.",
"TaskRefreshLibrary": "افحص مكتبة الوسائط",
- "TaskRefreshChapterImagesDescription": "إنشاء صور مصغرة لمقاطع الفيديو ذات فصول.",
+ "TaskRefreshChapterImagesDescription": "يُنشئ صور مصغرة لمقاطع الفيديو التي تحتوي على فصول.",
"TaskRefreshChapterImages": "استخراج صور الفصل",
"TasksApplicationCategory": "تطبيق",
- "TaskDownloadMissingSubtitlesDescription": "ابحث في الإنترنت على الترجمات المفقودة إستنادا على الميتاداتا.",
- "TaskDownloadMissingSubtitles": "تحميل الترجمات المفقودة",
- "TaskRefreshChannelsDescription": "تحديث معلومات قنوات الإنترنت.",
+ "TaskDownloadMissingSubtitlesDescription": "يبحث في الإنترنت على الترجمات الناقصة استنادا على البيانات الوصفية.",
+ "TaskDownloadMissingSubtitles": "تحميل الترجمات الناقصة",
+ "TaskRefreshChannelsDescription": "يحدث معلومات قنوات الإنترنت.",
"TaskRefreshChannels": "إعادة تحديث القنوات",
- "TaskCleanTranscodeDescription": "حذف ملفات الترميز الأقدم من يوم واحد.",
- "TaskCleanTranscode": "حذف سجلات الترميز",
+ "TaskCleanTranscodeDescription": "يحذف ملفات الترميز الأقدم من يوم واحد.",
+ "TaskCleanTranscode": "حذف ما بمجلد الترميز",
"TaskUpdatePluginsDescription": "تحميل وتثبيت الإضافات التي تم تفعيل التحديث التلقائي لها.",
"TaskUpdatePlugins": "تحديث الإضافات",
- "TaskRefreshPeopleDescription": "تحديث البيانات الوصفية للممثلين والمخرجين في مكتبة الوسائط الخاصة بك.",
+ "TaskRefreshPeopleDescription": "يقوم بتحديث البيانات الوصفية للممثلين والمخرجين في مكتبة الوسائط الخاصة بك.",
"TaskRefreshPeople": "إعادة تحميل الأشخاص",
- "TaskCleanLogsDescription": "حذف السجلات الأقدم من {0} يوم.",
- "TaskCleanLogs": "حذف دليل السجل",
- "TaskCleanActivityLogDescription": "يحذف سجل الأنشطة الأقدم من الوقت الموضوع.",
+ "TaskCleanLogsDescription": "يحذف السجلات الأقدم من {0} يوم.",
+ "TaskCleanLogs": "حذف مسار السجل",
+ "TaskCleanActivityLogDescription": "يحذف سجل الأنشطة الأقدم من الوقت الذي تم تحديده.",
"TaskCleanActivityLog": "حذف سجل الأنشطة",
- "Default": "الإعدادات الافتراضية",
+ "Default": "افتراضي",
"Undefined": "غير معرف",
"Forced": "ملحقة",
- "TaskOptimizeDatabaseDescription": "يضغط قاعدة البيانات ويقتطع المساحة الحرة. تشغيل هذه المهمة بعد فحص المكتبة أو إجراء تغييرات أخرى تشير ضمنًا إلى أن تعديلات قاعدة البيانات قد تؤدي إلى تحسين الأداء.",
- "TaskOptimizeDatabase": "تحسين قاعدة البيانات"
+ "TaskOptimizeDatabaseDescription": "يضغط قاعدة البيانات ويقتطع المساحة الحرة. تشغيل هذه المهمة بعد فحص المكتبة أو إجراء تغييرات أخرى تتضمن تعديلات في قاعدة البيانات قد تؤدي إلى تحسين الأداء.",
+ "TaskOptimizeDatabase": "تحسين قاعدة البيانات",
+ "TaskKeyframeExtractorDescription": "يستخرج الإطارات الرئيسية من ملفات الفيديو لكي ينشئ قوائم تشغيل بث HTTP المباشر. قد تستمر هذه العملية لوقت طويل.",
+ "TaskKeyframeExtractor": "مستخرج الإطار الرئيسي",
+ "External": "خارجي"
}
diff --git a/Emby.Server.Implementations/Localization/Core/bg-BG.json b/Emby.Server.Implementations/Localization/Core/bg-BG.json
index e1c923308..64cb36fd8 100644
--- a/Emby.Server.Implementations/Localization/Core/bg-BG.json
+++ b/Emby.Server.Implementations/Localization/Core/bg-BG.json
@@ -43,7 +43,7 @@
"NameInstallFailed": "{0} не можа да се инсталира",
"NameSeasonNumber": "Сезон {0}",
"NameSeasonUnknown": "Неразпознат сезон",
- "NewVersionIsAvailable": "Нова версия на Jellyfin сървъра е достъпна за сваляне.",
+ "NewVersionIsAvailable": "Нова версия на Джелифин сървъра е достъпна за сваляне.",
"NotificationOptionApplicationUpdateAvailable": "Налично е обновление на програмата",
"NotificationOptionApplicationUpdateInstalled": "Обновлението на програмата е инсталирано",
"NotificationOptionAudioPlayback": "Възпроизвеждането на звук започна",
@@ -120,5 +120,8 @@
"TaskCleanActivityLogDescription": "Изтрива записите в дневника с активност по стари от конфигурираната възраст.",
"TaskCleanActivityLog": "Изчисти дневника с активност",
"TaskOptimizeDatabaseDescription": "Прави базата данни по-компактна и освобождава място. Пускането на тази задача след сканиране на библиотеката или правене на други промени, свързани с модификации на базата данни, може да подобри производителността.",
- "TaskOptimizeDatabase": "Оптимизирай базата данни"
+ "TaskOptimizeDatabase": "Оптимизирай базата данни",
+ "TaskKeyframeExtractorDescription": "Извличат се ключови кадри от видеофайловете ,за да се създаде по точен ХЛС списък . Задачата може да отнеме много време.",
+ "TaskKeyframeExtractor": "Извличане на ключови кадри",
+ "External": "Външен"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json
index 502c562a5..644d2676e 100644
--- a/Emby.Server.Implementations/Localization/Core/ca.json
+++ b/Emby.Server.Implementations/Localization/Core/ca.json
@@ -20,7 +20,7 @@
"HeaderFavoriteAlbums": "Àlbums Preferits",
"HeaderFavoriteArtists": "Artistes Predilectes",
"HeaderFavoriteEpisodes": "Episodis Predilectes",
- "HeaderFavoriteShows": "Programes Predilectes",
+ "HeaderFavoriteShows": "Sèries Predilectes",
"HeaderFavoriteSongs": "Cançons Predilectes",
"HeaderLiveTV": "TV en Directe",
"HeaderNextUp": "A continuació",
@@ -70,7 +70,7 @@
"ScheduledTaskFailedWithName": "{0} ha fallat",
"ScheduledTaskStartedWithName": "{0} iniciat",
"ServerNameNeedsToBeRestarted": "{0} necessita ser reiniciat",
- "Shows": "Programes",
+ "Shows": "Sèries",
"Songs": "Cançons",
"StartupEmbyServerIsLoading": "El Servidor d'Jellyfin est&agrave; carregant. Si et plau, prova de nou en breus.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json
index 722b81c8a..9c278db4d 100644
--- a/Emby.Server.Implementations/Localization/Core/de.json
+++ b/Emby.Server.Implementations/Localization/Core/de.json
@@ -121,7 +121,7 @@
"Default": "Standard",
"TaskOptimizeDatabaseDescription": "Komprimiert die Datenbank und trimmt den freien Speicherplatz. Die Ausführung dieser Aufgabe nach dem Scannen der Bibliothek oder nach anderen Änderungen, die Datenbankänderungen implizieren, kann die Leistung verbessern.",
"TaskOptimizeDatabase": "Datenbank optimieren",
- "TaskKeyframeExtractorDescription": "Extrahiere Keyframes aus Videodateien, um präzisere HLS-Playlisten zu erzeugen. Diese Aufgabe kann sehr lange dauern.",
+ "TaskKeyframeExtractorDescription": "Extrahiere Keyframes aus Videodateien, um präzisere HLS-Playlisten zu erzeugen. Dieser Vorgang kann sehr lange dauern.",
"TaskKeyframeExtractor": "Keyframe Extraktor",
"External": "Extern"
}
diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json
index acf42f38e..9e216a166 100644
--- a/Emby.Server.Implementations/Localization/Core/el.json
+++ b/Emby.Server.Implementations/Localization/Core/el.json
@@ -5,17 +5,17 @@
"Artists": "Καλλιτέχνες",
"AuthenticationSucceededWithUserName": "Ο χρήστης {0} επαληθεύτηκε επιτυχώς",
"Books": "Βιβλία",
- "CameraImageUploadedFrom": "Μια νέα εικόνα κάμερας έχει αποσταλεί από {0}",
+ "CameraImageUploadedFrom": "Μια νέα φωτογραφία φορτώθηκε από {0}",
"Channels": "Κανάλια",
"ChapterNameValue": "Κεφάλαιο {0}",
"Collections": "Συλλογές",
- "DeviceOfflineWithName": "{0} αποσυνδέθηκε",
- "DeviceOnlineWithName": "{0} συνδέθηκε",
+ "DeviceOfflineWithName": "Ο/Η {0} αποσυνδέθηκε",
+ "DeviceOnlineWithName": "Ο/Η {0} συνδέθηκε",
"FailedLoginAttemptWithUserName": "Αποτυχημένη προσπάθεια σύνδεσης από {0}",
"Favorites": "Αγαπημένα",
"Folders": "Φάκελοι",
"Genres": "Είδη",
- "HeaderAlbumArtists": "Καλλιτέχνες άλμπουμ",
+ "HeaderAlbumArtists": "Δισκογραφικοί καλλιτέχνες",
"HeaderContinueWatching": "Συνεχίστε την παρακολούθηση",
"HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ",
"HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες",
@@ -24,7 +24,7 @@
"HeaderFavoriteSongs": "Αγαπημένα Τραγούδια",
"HeaderLiveTV": "Ζωντανή Τηλεόραση",
"HeaderNextUp": "Επόμενο",
- "HeaderRecordingGroups": "Γκρουπ Εγγραφών",
+ "HeaderRecordingGroups": "Μουσικά Συγκροτήματα",
"HomeVideos": "Προσωπικά βίντεο",
"Inherit": "Κληρονόμηση",
"ItemAddedWithName": "{0} προστέθηκε στη βιβλιοθήκη",
@@ -32,10 +32,10 @@
"LabelIpAddressValue": "Διεύθυνση IP: {0}",
"LabelRunningTimeValue": "Διάρκεια: {0}",
"Latest": "Πρόσφατα",
- "MessageApplicationUpdated": "Ο Jellyfin Server έχει ενημερωθεί",
- "MessageApplicationUpdatedTo": "Ο server Jellyfin αναβαθμίστηκε σε έκδοση {0}",
- "MessageNamedServerConfigurationUpdatedWithValue": "Η ενότητα {0} ρύθμισης παραμέτρων του server έχει ενημερωθεί",
- "MessageServerConfigurationUpdated": "Η ρύθμιση παραμέτρων του server έχει ενημερωθεί",
+ "MessageApplicationUpdated": "Ο διακομιστής Jellyfin έχει ενημερωθεί",
+ "MessageApplicationUpdatedTo": "Ο διακομιστής Jellyfin αναβαθμίστηκε στην έκδοση {0}",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Η ενότητα {0} ρύθμισης παραμέτρων του διακομιστή έχει ενημερωθεί",
+ "MessageServerConfigurationUpdated": "Η ρύθμιση παραμέτρων του διακομιστή έχει ενημερωθεί",
"MixedContent": "Ανάμεικτο Περιεχόμενο",
"Movies": "Ταινίες",
"Music": "Μουσική",
@@ -43,7 +43,7 @@
"NameInstallFailed": "{0} η εγκατάσταση απέτυχε",
"NameSeasonNumber": "Κύκλος {0}",
"NameSeasonUnknown": "Άγνωστος Κύκλος",
- "NewVersionIsAvailable": "Μια νέα έκδοση του Jellyfin Server είναι διαθέσιμη για λήψη.",
+ "NewVersionIsAvailable": "Μια νέα έκδοση του διακομιστή Jellyfin είναι διαθέσιμη για λήψη.",
"NotificationOptionApplicationUpdateAvailable": "Διαθέσιμη ενημερωμένη έκδοση εφαρμογής",
"NotificationOptionApplicationUpdateInstalled": "Η ενημέρωση εφαρμογής εγκαταστάθηκε",
"NotificationOptionAudioPlayback": "Η αναπαραγωγή ήχου ξεκίνησε",
@@ -55,7 +55,7 @@
"NotificationOptionPluginInstalled": "Το plugin εγκαταστάθηκε",
"NotificationOptionPluginUninstalled": "Το plugin απεγκαταστάθηκε",
"NotificationOptionPluginUpdateInstalled": "Η αναβάθμιση του plugin εγκαταστάθηκε",
- "NotificationOptionServerRestartRequired": "Απαιτείται επανεκκίνηση του server",
+ "NotificationOptionServerRestartRequired": "Ο διακομιστής χρειάζεται επανεκκίνηση",
"NotificationOptionTaskFailed": "Αποτυχία προγραμματισμένης εργασίας",
"NotificationOptionUserLockedOut": "Ο χρήστης αποκλείστηκε",
"NotificationOptionVideoPlayback": "Η αναπαραγωγή βίντεο ξεκίνησε",
@@ -72,7 +72,7 @@
"ServerNameNeedsToBeRestarted": "{0} χρειάζεται επανεκκίνηση",
"Shows": "Σειρές",
"Songs": "Τραγούδια",
- "StartupEmbyServerIsLoading": "Ο Jellyfin Server φορτώνει. Παρακαλώ δοκιμάστε σε λίγο.",
+ "StartupEmbyServerIsLoading": "Ο διακομιστής Jellyfin φορτώνει. Περιμένετε λίγο και δοκιμάστε ξανά.",
"SubtitleDownloadFailureForItem": "Οι υπότιτλοι απέτυχαν να κατέβουν για {0}",
"SubtitleDownloadFailureFromForItem": "Αποτυχίες μεταφόρτωσης υποτίτλων από {0} για {1}",
"Sync": "Συγχρονισμός",
@@ -121,7 +121,7 @@
"Default": "Προεπιλογή",
"TaskOptimizeDatabaseDescription": "Συμπιέζει τη βάση δεδομένων και δημιουργεί ελεύθερο χώρο. Η εκτέλεση αυτής της εργασίας μετά τη σάρωση της βιβλιοθήκης ή την πραγματοποίηση άλλων αλλαγών που συνεπάγονται τροποποιήσεις της βάσης δεδομένων μπορεί να βελτιώσει την απόδοση.",
"TaskOptimizeDatabase": "Βελτιστοποίηση βάσης δεδομένων",
- "TaskKeyframeExtractorDescription": "Εξάγει τα βασικά καρέ από αρχεία βίντεο για να δημιουργήσει πιο ακριβείς HLS λίστες αναπαραγωγής. Αυτή η εργασία μπορεί να διαρκέσει πολλή ώρα.",
+ "TaskKeyframeExtractorDescription": "Εξάγει καρέ από αρχεία βίντεο για να δημιουργήσει πιο ακριβείς λίστες αναπαραγωγής HLS. Αυτή η διεργασία μπορεί να πάρει χρόνο.",
"TaskKeyframeExtractor": "Εξαγωγέας βασικών καρέ βίντεο",
"External": "Εξωτερικό"
}
diff --git a/Emby.Server.Implementations/Localization/Core/es-AR.json b/Emby.Server.Implementations/Localization/Core/es-AR.json
index 4d9f5f41b..1289172ba 100644
--- a/Emby.Server.Implementations/Localization/Core/es-AR.json
+++ b/Emby.Server.Implementations/Localization/Core/es-AR.json
@@ -121,5 +121,7 @@
"Default": "Por Defecto",
"TaskOptimizeDatabaseDescription": "Compacta la base de datos y restaura el espacio libre. Ejecutar esta tarea después de actualizar las librerías o realizar otros cambios que impliquen modificar las bases de datos puede mejorar la performance.",
"TaskOptimizeDatabase": "Optimización de base de datos",
- "External": "Externo"
+ "External": "Externo",
+ "TaskKeyframeExtractorDescription": "Extrae Fotogramas Clave de los archivos de vídeo para crear Listas de Reprodución HLS más precisas. Esta tarea puede durar mucho tiempo.",
+ "TaskKeyframeExtractor": "Extractor de Fotogramas Clave"
}
diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json
index 80ae16c5c..a7391cc88 100644
--- a/Emby.Server.Implementations/Localization/Core/es-MX.json
+++ b/Emby.Server.Implementations/Localization/Core/es-MX.json
@@ -122,5 +122,6 @@
"TaskOptimizeDatabase": "Optimizar base de datos",
"TaskOptimizeDatabaseDescription": "Compacta la base de datos y trunca el espacio libre. Puede mejorar el rendimiento si se realiza esta tarea después de escanear la biblioteca o después de realizar otros cambios que impliquen modificar la base de datos.",
"TaskKeyframeExtractorDescription": "Extrae los cuadros clave de los archivos de vídeo para crear listas HLS más precisas. Esta tarea puede tardar un buen rato.",
- "TaskKeyframeExtractor": "Extractor de Cuadros Clave"
+ "TaskKeyframeExtractor": "Extractor de Cuadros Clave",
+ "External": "Externo"
}
diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json
index 0821ddda7..db65a0c6d 100644
--- a/Emby.Server.Implementations/Localization/Core/es.json
+++ b/Emby.Server.Implementations/Localization/Core/es.json
@@ -74,7 +74,7 @@
"Songs": "Canciones",
"StartupEmbyServerIsLoading": "Jellyfin Server se está cargando. Vuelve a intentarlo en breve.",
"SubtitleDownloadFailureForItem": "Error al descargar subtítulos para {0}",
- "SubtitleDownloadFailureFromForItem": "Fallo de descarga de subtítulos desde {0} para {1}",
+ "SubtitleDownloadFailureFromForItem": "Fallo en la descarga de subtítulos desde {0} para {1}",
"Sync": "Sincronizar",
"System": "Sistema",
"TvShows": "Series",
@@ -93,19 +93,19 @@
"ValueSpecialEpisodeName": "Especial - {0}",
"VersionNumber": "Versión {0}",
"TasksMaintenanceCategory": "Mantenimiento",
- "TasksLibraryCategory": "Librería",
+ "TasksLibraryCategory": "Biblioteca",
"TasksApplicationCategory": "Aplicación",
"TasksChannelsCategory": "Canales de internet",
"TaskCleanCache": "Eliminar archivos temporales",
"TaskCleanCacheDescription": "Elimina los archivos temporales que ya no son necesarios para el servidor.",
"TaskRefreshChapterImages": "Extraer imágenes de los capítulos",
- "TaskRefreshChapterImagesDescription": "Crea las miniaturas de los vídeos que tengan capítulos.",
+ "TaskRefreshChapterImagesDescription": "Crear miniaturas de los vídeos que tengan capítulos.",
"TaskRefreshLibrary": "Escanear la biblioteca",
"TaskRefreshLibraryDescription": "Añade los archivos que se hayan añadido a la biblioteca y actualiza las etiquetas de los ya presentes.",
"TaskCleanLogs": "Limpiar registros",
"TaskCleanLogsDescription": "Elimina los archivos de registro que tengan más de {0} días.",
"TaskRefreshPeople": "Actualizar personas",
- "TaskRefreshPeopleDescription": "Actualiza las etiquetas de los intérpretes y directores presentes en tus bibliotecas.",
+ "TaskRefreshPeopleDescription": "Actualiza las etiquetas de los actores y directores presentes en tus bibliotecas.",
"TaskUpdatePlugins": "Actualizar extensiones",
"TaskUpdatePluginsDescription": "Actualiza las extensiones que están configuradas para actualizarse automáticamente.",
"TaskCleanTranscode": "Limpiar las transcodificaciones",
@@ -120,7 +120,7 @@
"Forced": "Forzado",
"Default": "Predeterminado",
"TaskOptimizeDatabase": "Optimizar la base de datos",
- "TaskOptimizeDatabaseDescription": "Compacta y libera el espacio libre en la base de datos. Ejecutar esta tarea tras escanear la biblioteca o hacer cambios que impliquen modificaciones en la base de datos puede mejorar el rendimiento.",
+ "TaskOptimizeDatabaseDescription": "Optimiza y libera el espacio libre en la base de datos. Ejecutar esta tarea tras escanear la biblioteca o hacer cambios que impliquen modificaciones en la base de datos puede mejorar el rendimiento.",
"TaskKeyframeExtractorDescription": "Extrae los fotogramas clave de los archivos de vídeo para crear listas HLS más precisas. Esta tarea puede tardar mucho tiempo.",
"TaskKeyframeExtractor": "Extractor de Fotogramas Clave",
"External": "Externo"
diff --git a/Emby.Server.Implementations/Localization/Core/es_419.json b/Emby.Server.Implementations/Localization/Core/es_419.json
index 2ca736ad9..d6078c9c6 100644
--- a/Emby.Server.Implementations/Localization/Core/es_419.json
+++ b/Emby.Server.Implementations/Localization/Core/es_419.json
@@ -119,5 +119,8 @@
"Forced": "Forzado",
"Default": "Por defecto",
"TaskOptimizeDatabaseDescription": "Compacta la base de datos y libera espacio. Ejecutar esta tarea después de escanear la biblioteca o hacer otros cambios que impliquen modificaciones en la base de datos puede mejorar el rendimiento.",
- "TaskOptimizeDatabase": "Optimizar base de datos"
+ "TaskOptimizeDatabase": "Optimizar base de datos",
+ "External": "Externo",
+ "TaskKeyframeExtractorDescription": "Extrae Fotogramas Clave de los archivos de vídeo para crear Listas de Reproducción HLS más precisas. Esta tarea puede durar mucho tiempo.",
+ "TaskKeyframeExtractor": "Extractor de Fotogramas Clave"
}
diff --git a/Emby.Server.Implementations/Localization/Core/es_DO.json b/Emby.Server.Implementations/Localization/Core/es_DO.json
index b64ffbfbb..0f4c7438f 100644
--- a/Emby.Server.Implementations/Localization/Core/es_DO.json
+++ b/Emby.Server.Implementations/Localization/Core/es_DO.json
@@ -19,5 +19,7 @@
"FailedLoginAttemptWithUserName": "Intento de inicio de sesión fallido de {0}",
"HeaderFavoriteSongs": "Canciones Favoritas",
"HeaderFavoriteEpisodes": "Episodios Favoritos",
- "HeaderFavoriteArtists": "Artistas Favoritos"
+ "HeaderFavoriteArtists": "Artistas Favoritos",
+ "External": "Externo",
+ "Default": "Predeterminado"
}
diff --git a/Emby.Server.Implementations/Localization/Core/et.json b/Emby.Server.Implementations/Localization/Core/et.json
index 8db6a0b38..da44e53d0 100644
--- a/Emby.Server.Implementations/Localization/Core/et.json
+++ b/Emby.Server.Implementations/Localization/Core/et.json
@@ -119,5 +119,6 @@
"SubtitleDownloadFailureFromForItem": "Subtiitrite allalaadimine {0} > {1} nurjus",
"UserPolicyUpdatedWithName": "Kasutaja {0} õigusi värskendati",
"UserStoppedPlayingItemWithValues": "{0} lõpetas {1} taasesituse seadmes {2}",
- "UserOnlineFromDevice": "{0} on ühendatud seadmest {1}"
+ "UserOnlineFromDevice": "{0} on ühendatud seadmest {1}",
+ "External": "Väline"
}
diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json
index 8675ab2e6..f0cafd1c0 100644
--- a/Emby.Server.Implementations/Localization/Core/fi.json
+++ b/Emby.Server.Implementations/Localization/Core/fi.json
@@ -1,5 +1,5 @@
{
- "HeaderLiveTV": "Suora TV",
+ "HeaderLiveTV": "Televisio",
"NewVersionIsAvailable": "Uusi versio Jellyfin-palvelimesta on ladattavissa.",
"NameSeasonUnknown": "Tuntematon kausi",
"NameSeasonNumber": "Kausi {0}",
diff --git a/Emby.Server.Implementations/Localization/Core/fr-CA.json b/Emby.Server.Implementations/Localization/Core/fr-CA.json
index 2a56d0745..24ca8f861 100644
--- a/Emby.Server.Implementations/Localization/Core/fr-CA.json
+++ b/Emby.Server.Implementations/Localization/Core/fr-CA.json
@@ -120,5 +120,8 @@
"Undefined": "Indéfini",
"Forced": "Forcé",
"TaskOptimizeDatabaseDescription": "Compacte la base de données et tronque l'espace libre. Lancer cette tâche après avoir scanné la bibliothèque ou faire d'autres changements impliquant des modifications de la base peuvent ameliorer les performances.",
- "TaskOptimizeDatabase": "Optimiser la base de données"
+ "TaskOptimizeDatabase": "Optimiser la base de données",
+ "TaskKeyframeExtractorDescription": "Extrait les images clés des fichiers vidéo pour créer des listes de lecture HLS plus précises. Cette tâche peut durer très longtemps.",
+ "TaskKeyframeExtractor": "Extracteur d'image clé",
+ "External": "Externe"
}
diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json
index 2a329e74d..648c878e9 100644
--- a/Emby.Server.Implementations/Localization/Core/fr.json
+++ b/Emby.Server.Implementations/Localization/Core/fr.json
@@ -5,7 +5,7 @@
"Artists": "Artistes",
"AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
"Books": "Livres",
- "CameraImageUploadedFrom": "Une photo a été chargée depuis {0}",
+ "CameraImageUploadedFrom": "Une photo a été téléversée depuis {0}",
"Channels": "Chaînes",
"ChapterNameValue": "Chapitre {0}",
"Collections": "Collections",
@@ -42,13 +42,13 @@
"MusicVideos": "Clips musicaux",
"NameInstallFailed": "{0} échec de l'installation",
"NameSeasonNumber": "Saison {0}",
- "NameSeasonUnknown": "Saison Inconnue",
+ "NameSeasonUnknown": "Saison inconnue",
"NewVersionIsAvailable": "Une nouvelle version de Jellyfin Serveur est disponible au téléchargement.",
"NotificationOptionApplicationUpdateAvailable": "Mise à jour de l'application disponible",
"NotificationOptionApplicationUpdateInstalled": "Mise à jour de l'application installée",
"NotificationOptionAudioPlayback": "Lecture audio démarrée",
"NotificationOptionAudioPlaybackStopped": "Lecture audio arrêtée",
- "NotificationOptionCameraImageUploaded": "L'image de l'appareil photo a été transférée",
+ "NotificationOptionCameraImageUploaded": "L'image de l'appareil photo a été téléversée",
"NotificationOptionInstallationFailed": "Échec de l'installation",
"NotificationOptionNewLibraryContent": "Nouveau contenu ajouté",
"NotificationOptionPluginError": "Erreur d'extension",
@@ -70,8 +70,8 @@
"ScheduledTaskFailedWithName": "{0} a échoué",
"ScheduledTaskStartedWithName": "{0} a démarré",
"ServerNameNeedsToBeRestarted": "{0} doit être redémarré",
- "Shows": "Émissions",
- "Songs": "Chansons",
+ "Shows": "Séries",
+ "Songs": "Titres",
"StartupEmbyServerIsLoading": "Le serveur Jellyfin est en cours de chargement. Veuillez réessayer dans quelques instants.",
"SubtitleDownloadFailureForItem": "Le téléchargement des sous-titres pour {0} a échoué.",
"SubtitleDownloadFailureFromForItem": "Échec du téléchargement des sous-titres depuis {0} pour {1}",
@@ -92,35 +92,36 @@
"ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre médiathèque",
"ValueSpecialEpisodeName": "Spécial - {0}",
"VersionNumber": "Version {0}",
- "TasksChannelsCategory": "Chaines en ligne",
- "TaskDownloadMissingSubtitlesDescription": "Recherche les sous-titres manquants sur internet en se basant sur la configuration des métadonnées.",
+ "TasksChannelsCategory": "Chaînes en ligne",
+ "TaskDownloadMissingSubtitlesDescription": "Recherche les sous-titres manquants sur Internet en se basant sur la configuration des métadonnées.",
"TaskDownloadMissingSubtitles": "Télécharger les sous-titres manquants",
- "TaskRefreshChannelsDescription": "Rafraîchit les informations des chaines en ligne.",
- "TaskRefreshChannels": "Rafraîchir les chaines",
+ "TaskRefreshChannelsDescription": "Actualise les informations des chaînes en ligne.",
+ "TaskRefreshChannels": "Actualiser les chaînes",
"TaskCleanTranscodeDescription": "Supprime les fichiers transcodés de plus d'un jour.",
- "TaskCleanTranscode": "Nettoyer les dossier des transcodages",
+ "TaskCleanTranscode": "Nettoyer le dossier des transcodages",
"TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des extensions configurées pour être mises à jour automatiquement.",
"TaskUpdatePlugins": "Mettre à jour les extensions",
- "TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et réalisateurs dans votre bibliothèque.",
- "TaskRefreshPeople": "Rafraîchir les acteurs",
+ "TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et réalisateurs dans votre médiathèque.",
+ "TaskRefreshPeople": "Actualiser les acteurs",
"TaskCleanLogsDescription": "Supprime les journaux de plus de {0} jours.",
"TaskCleanLogs": "Nettoyer le répertoire des journaux",
- "TaskRefreshLibraryDescription": "Scanne votre médiathèque pour trouver les nouveaux fichiers et rafraîchit les métadonnées.",
+ "TaskRefreshLibraryDescription": "Scanne votre médiathèque pour trouver les nouveaux fichiers et actualise les métadonnées.",
"TaskRefreshLibrary": "Scanner la médiathèque",
"TaskRefreshChapterImagesDescription": "Crée des vignettes pour les vidéos ayant des chapitres.",
"TaskRefreshChapterImages": "Extraire les images de chapitre",
"TaskCleanCacheDescription": "Supprime les fichiers de cache dont le système n'a plus besoin.",
"TaskCleanCache": "Vider le répertoire cache",
"TasksApplicationCategory": "Application",
- "TasksLibraryCategory": "Bibliothèque",
+ "TasksLibraryCategory": "Médiathèque",
"TasksMaintenanceCategory": "Maintenance",
"TaskCleanActivityLogDescription": "Supprime les entrées du journal d'activité antérieures à l'âge configuré.",
"TaskCleanActivityLog": "Nettoyer le journal d'activité",
"Undefined": "Non défini",
"Forced": "Forcé",
"Default": "Par défaut",
- "TaskOptimizeDatabaseDescription": "Réduit les espaces vides/inutiles et compacte la base de données. Utiliser cette fonction après une mise à jour de la bibliothèque ou toute autre modification de la base de données peut améliorer les performances du serveur.",
+ "TaskOptimizeDatabaseDescription": "Réduit les espaces vides ou inutiles et compacte la base de données. Utiliser cette fonction après une mise à jour de la médiathèque ou toute autre modification de la base de données peut améliorer les performances du serveur.",
"TaskOptimizeDatabase": "Optimiser la base de données",
"TaskKeyframeExtractorDescription": "Extrait les images clés des fichiers vidéo pour créer des listes de lecture HLS plus précises. Cette tâche peut durer très longtemps.",
- "TaskKeyframeExtractor": "Extracteur d'image clé"
+ "TaskKeyframeExtractor": "Extracteur d'image clé",
+ "External": "Externe"
}
diff --git a/Emby.Server.Implementations/Localization/Core/gsw.json b/Emby.Server.Implementations/Localization/Core/gsw.json
index 5bfe8c0b1..bd8cec710 100644
--- a/Emby.Server.Implementations/Localization/Core/gsw.json
+++ b/Emby.Server.Implementations/Localization/Core/gsw.json
@@ -119,5 +119,6 @@
"Undefined": "Undefiniert",
"Forced": "Erzwungen",
"Default": "Standard",
- "TaskOptimizeDatabase": "Datenbank optimieren"
+ "TaskOptimizeDatabase": "Datenbank optimieren",
+ "External": "Extern"
}
diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json
index e32ab4ca8..c635dab23 100644
--- a/Emby.Server.Implementations/Localization/Core/he.json
+++ b/Emby.Server.Implementations/Localization/Core/he.json
@@ -120,5 +120,8 @@
"Forced": "כפוי",
"Default": "ברירת מחדל",
"TaskOptimizeDatabase": "מיטוב מסד נתונים",
- "TaskOptimizeDatabaseDescription": "דוחס את מסד הנתונים ומוריד את שטח האחסון שבשימוש. הרצה של פעולה זו לאחר סריקת הספרייה או שינויים אחרים שמשפיעים על מסד הנתונים יכולה לשפר ביצועים."
+ "TaskOptimizeDatabaseDescription": "דוחס את מסד הנתונים ומוריד את שטח האחסון שבשימוש. הרצה של פעולה זו לאחר סריקת הספרייה או שינויים אחרים שמשפיעים על מסד הנתונים יכולה לשפר ביצועים.",
+ "TaskKeyframeExtractorDescription": "חלץ תמונות מפתח מקבצי וידאו בכדי ליצור רשימות השמעה מדויקות יותר של HLS. משימה זו עלולה להימשך זמן רב.",
+ "TaskKeyframeExtractor": "מחלץ תמונות מפתח",
+ "External": "חיצוני"
}
diff --git a/Emby.Server.Implementations/Localization/Core/hi.json b/Emby.Server.Implementations/Localization/Core/hi.json
index 781cfcfa2..182b43ffc 100644
--- a/Emby.Server.Implementations/Localization/Core/hi.json
+++ b/Emby.Server.Implementations/Localization/Core/hi.json
@@ -66,5 +66,6 @@
"PluginInstalledWithName": "{0} इंस्टॉल हुए",
"Plugin": "प्लग-इन",
"Playlists": "प्लेलिस्ट",
- "Photos": "तस्वीरें"
+ "Photos": "तस्वीरें",
+ "External": "बाहरी"
}
diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json
index 4df0444e6..c63cd2b94 100644
--- a/Emby.Server.Implementations/Localization/Core/hr.json
+++ b/Emby.Server.Implementations/Localization/Core/hr.json
@@ -118,5 +118,10 @@
"TaskCleanActivityLog": "Očisti dnevnik aktivnosti",
"Undefined": "Nedefinirano",
"Forced": "Forsirani",
- "Default": "Zadano"
+ "Default": "Zadano",
+ "TaskOptimizeDatabase": "Optimiziraj bazu podataka",
+ "External": "Vanjski",
+ "TaskKeyframeExtractorDescription": "Izvlačenje ključnih okvira iz videozapisa za stvaranje objektivnije HLS liste za reprodukciju. Pokretanje ovog zadatka može potrajati.",
+ "TaskKeyframeExtractor": "Izvoditelj ključnog okvira",
+ "TaskOptimizeDatabaseDescription": "Sažima bazu podataka i uklanja prazan prostor. Pokretanje ovog zadatka, može poboljšati performanse nakon provođenja indeksiranja biblioteke ili provođenja drugih promjena koje utječu na bazu podataka."
}
diff --git a/Emby.Server.Implementations/Localization/Core/id.json b/Emby.Server.Implementations/Localization/Core/id.json
index a32b55b5a..3e05525c8 100644
--- a/Emby.Server.Implementations/Localization/Core/id.json
+++ b/Emby.Server.Implementations/Localization/Core/id.json
@@ -36,7 +36,7 @@
"Songs": "Lagu",
"Playlists": "Daftar putar",
"NotificationOptionPluginUninstalled": "Plugin dihapus",
- "MusicVideos": "Video musik",
+ "MusicVideos": "Video Musik",
"VersionNumber": "Versi {0}",
"ValueSpecialEpisodeName": "Spesial - {0}",
"ValueHasBeenAddedToLibrary": "{0} telah ditambahkan ke pustaka media Anda",
diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json
index 2feff0922..2aa84c536 100644
--- a/Emby.Server.Implementations/Localization/Core/it.json
+++ b/Emby.Server.Implementations/Localization/Core/it.json
@@ -122,5 +122,6 @@
"TaskOptimizeDatabaseDescription": "Compatta Database e tronca spazi liberi. Eseguire questa azione dopo la scansione o dopo aver fatto altri cambiamenti inerenti il database potrebbe aumentarne la performance.",
"TaskOptimizeDatabase": "Ottimizza Database",
"TaskKeyframeExtractor": "Estrattore di Keyframe",
- "TaskKeyframeExtractorDescription": "Estrae i keyframe dai video per creare migliori playlist HLS. Questa procedura potrebbe richiedere molto tempo."
+ "TaskKeyframeExtractorDescription": "Estrae i keyframe dai video per creare migliori playlist HLS. Questa procedura potrebbe richiedere molto tempo.",
+ "External": "Esterno"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json
index 8cab706be..d90d705b2 100644
--- a/Emby.Server.Implementations/Localization/Core/ja.json
+++ b/Emby.Server.Implementations/Localization/Core/ja.json
@@ -16,7 +16,7 @@
"Folders": "フォルダー",
"Genres": "ジャンル",
"HeaderAlbumArtists": "アルバムアーティスト",
- "HeaderContinueWatching": "続きを見る",
+ "HeaderContinueWatching": "続けて見る",
"HeaderFavoriteAlbums": "お気に入りのアルバム",
"HeaderFavoriteArtists": "お気に入りのアーティスト",
"HeaderFavoriteEpisodes": "お気に入りのエピソード",
@@ -93,7 +93,7 @@
"VersionNumber": "バージョン {0}",
"TaskCleanLogsDescription": "{0} 日以上前のログを消去します。",
"TaskCleanLogs": "ログの掃除",
- "TaskRefreshLibraryDescription": "メディアライブラリをスキャンして新しいファイルを探し、メタデータをリフレッシュします。",
+ "TaskRefreshLibraryDescription": "メディアライブラリをスキャンして新しいファイルを探し、メタデータを更新します。",
"TaskRefreshLibrary": "メディアライブラリのスキャン",
"TaskCleanCacheDescription": "不要なキャッシュを消去します。",
"TaskCleanCache": "キャッシュを消去",
@@ -101,15 +101,15 @@
"TasksApplicationCategory": "アプリケーション",
"TasksLibraryCategory": "ライブラリ",
"TasksMaintenanceCategory": "メンテナンス",
- "TaskRefreshChannelsDescription": "ネットチャンネルの情報をリフレッシュします。",
- "TaskRefreshChannels": "チャンネルのリフレッシュ",
+ "TaskRefreshChannelsDescription": "ネットチャンネルの情報を更新する。",
+ "TaskRefreshChannels": "チャンネルの更新",
"TaskCleanTranscodeDescription": "1日以上経過したトランスコードファイルを削除します。",
"TaskCleanTranscode": "トランスコードディレクトリの削除",
"TaskUpdatePluginsDescription": "自動更新可能なプラグインのアップデートをダウンロードしてインストールします。",
"TaskUpdatePlugins": "プラグインの更新",
"TaskRefreshPeopleDescription": "メディアライブラリで俳優や監督のメタデータを更新します。",
"TaskRefreshPeople": "俳優や監督のデータの更新",
- "TaskDownloadMissingSubtitlesDescription": "メタデータ構成に基づいて、欠落している字幕をインターネットで検索します。",
+ "TaskDownloadMissingSubtitlesDescription": "メタデータ構成に基づいて、欠落している字幕をインターネットで検索する。",
"TaskRefreshChapterImagesDescription": "チャプターのあるビデオのサムネイルを作成します。",
"TaskRefreshChapterImages": "チャプター画像を抽出する",
"TaskDownloadMissingSubtitles": "不足している字幕をダウンロードする",
diff --git a/Emby.Server.Implementations/Localization/Core/kab.json b/Emby.Server.Implementations/Localization/Core/kab.json
new file mode 100644
index 000000000..9551f0e5c
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/kab.json
@@ -0,0 +1,14 @@
+{
+ "Music": "Aẓawan",
+ "Sync": "Amtawi",
+ "Photos": "Tiwlafin",
+ "Movies": "Isura",
+ "External": "Azɣaray",
+ "User": "Aseqdac",
+ "Folders": "Ikaramen",
+ "Favorites": "Ismenyifen",
+ "Default": "Lexṣas",
+ "Collections": "Tigrummiwin",
+ "Channels": "Ibuda",
+ "Albums": "Iseɣraz"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json
index 50d019f90..a4b2e75b3 100644
--- a/Emby.Server.Implementations/Localization/Core/ko.json
+++ b/Emby.Server.Implementations/Localization/Core/ko.json
@@ -3,14 +3,14 @@
"AppDeviceValues": "앱: {0}, 장치: {1}",
"Application": "애플리케이션",
"Artists": "아티스트",
- "AuthenticationSucceededWithUserName": "{0}이 성공적으로 인증됨",
+ "AuthenticationSucceededWithUserName": "{0}이(가) 성공적으로 인증됨",
"Books": "도서",
"CameraImageUploadedFrom": "{0}에서 새로운 카메라 이미지가 업로드됨",
"Channels": "채널",
"ChapterNameValue": "챕터 {0}",
"Collections": "컬렉션",
"DeviceOfflineWithName": "{0}의 연결 끊김",
- "DeviceOnlineWithName": "{0}이 연결됨",
+ "DeviceOnlineWithName": "{0}이(가) 연결됨",
"FailedLoginAttemptWithUserName": "{0}에서 로그인 실패",
"Favorites": "즐겨찾기",
"Folders": "폴더",
@@ -120,5 +120,8 @@
"Forced": "강제하기",
"Default": "기본 설정",
"TaskOptimizeDatabaseDescription": "데이터베이스를 압축하고 사용 가능한 공간을 늘립니다. 라이브러리를 검색한 후 이 작업을 실행하거나 데이터베이스 수정같은 비슷한 작업을 수행하면 성능이 향상될 수 있습니다.",
- "TaskOptimizeDatabase": "데이터베이스 최적화"
+ "TaskOptimizeDatabase": "데이터베이스 최적화",
+ "TaskKeyframeExtractorDescription": "비디오 파일에서 키프레임을 추출하여 더 정확한 HLS 재생 목록을 만듭니다. 이 작업은 오랫동안 진행될 수 있습니다.",
+ "TaskKeyframeExtractor": "키프레임 추출",
+ "External": "외부"
}
diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json
index cb98d8e41..232b3ec93 100644
--- a/Emby.Server.Implementations/Localization/Core/lt-LT.json
+++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json
@@ -39,7 +39,7 @@
"MixedContent": "Mixed content",
"Movies": "Filmai",
"Music": "Muzika",
- "MusicVideos": "Muzikiniai klipai",
+ "MusicVideos": "Muzikiniai vaizdo įrašai",
"NameInstallFailed": "{0} diegimo klaida",
"NameSeasonNumber": "Sezonas {0}",
"NameSeasonUnknown": "Sezonas neatpažintas",
diff --git a/Emby.Server.Implementations/Localization/Core/lv.json b/Emby.Server.Implementations/Localization/Core/lv.json
index 443a74a10..e460fd719 100644
--- a/Emby.Server.Implementations/Localization/Core/lv.json
+++ b/Emby.Server.Implementations/Localization/Core/lv.json
@@ -84,7 +84,7 @@
"CameraImageUploadedFrom": "Jauns kameras attēls ir ticis augšupielādēts no {0}",
"Books": "Grāmatas",
"Artists": "Izpildītāji",
- "Albums": "Albumi",
+ "Albums": "Albūmi",
"ProviderValue": "Provider: {0}",
"HeaderFavoriteSongs": "Dziesmu Favorīti",
"HeaderFavoriteShows": "Raidījumu Favorīti",
@@ -117,7 +117,8 @@
"TaskCleanActivityLogDescription": "Nodzēš darbību žurnāla ierakstus, kuri ir vecāki par doto vecumu.",
"TaskCleanActivityLog": "Notīrīt Darbību Žurnālu",
"Undefined": "Nenoteikts",
- "Default": "Noklusējums",
+ "Default": "Noklusējuma",
"TaskOptimizeDatabaseDescription": "Saspiež datubāzi un atbrīvo atmiņu. Uzdevum palaišana pēc bibliotēku skenēšanas vai citām, ar datubāzi saistītām, izmaiņām iespējams uzlabos ātrdarbību.",
- "TaskOptimizeDatabase": "Optimizēt datubāzi"
+ "TaskOptimizeDatabase": "Optimizēt datubāzi",
+ "External": "Ārējais"
}
diff --git a/Emby.Server.Implementations/Localization/Core/mk.json b/Emby.Server.Implementations/Localization/Core/mk.json
index 279734c5e..cbccad87f 100644
--- a/Emby.Server.Implementations/Localization/Core/mk.json
+++ b/Emby.Server.Implementations/Localization/Core/mk.json
@@ -64,9 +64,9 @@
"CameraImageUploadedFrom": "Нова слика од камера беше поставена од {0}",
"Books": "Книги",
"AuthenticationSucceededWithUserName": "{0} успешно поврзан",
- "Artists": "Изведувач",
+ "Artists": "Изведувачи",
"Application": "Апликација",
- "AppDeviceValues": "Аплиакција: {0}, Уред: {1}",
+ "AppDeviceValues": "Апликација: {0}, Уред: {1}",
"Albums": "Албуми",
"VersionNumber": "Верзија {0}",
"ValueSpecialEpisodeName": "Специјално - {0}",
@@ -100,5 +100,27 @@
"TasksMaintenanceCategory": "Одржување",
"Undefined": "Недефинирано",
"Forced": "Принудно",
- "Default": "Зададено"
+ "Default": "Зададено",
+ "TaskKeyframeExtractorDescription": "Извлекува клучни рамки од видео фајлови за да се направат попрецизни HLS плејлисти. Оваа задача може да работи многу долго време.",
+ "TaskKeyframeExtractor": "Извлекувач на клучни рамки",
+ "TaskOptimizeDatabaseDescription": "Компактира датабазата и смалува празното место. Извршувањето на оваа задача по скенирање на библиотеката или правење други промени што прават модификации на датабазата може да подобри перформансите.",
+ "TaskOptimizeDatabase": "Оптимизирај датабаза",
+ "TaskDownloadMissingSubtitlesDescription": "Пребарува интернет за преводи што недостиваат според метадата конфигурација.",
+ "TaskDownloadMissingSubtitles": "Симни преводи што недостигаат",
+ "TaskRefreshChannelsDescription": "Ажурирај информации за интернет канали.",
+ "TaskRefreshChannels": "Ажурирај Канали",
+ "TaskCleanTranscodeDescription": "Избриши транскодирани фајлови постари од еден ден.",
+ "TaskCleanTranscode": "Исчисти Директориум за Транскодирање",
+ "TaskUpdatePluginsDescription": "Симни и инсталирај ажурирања за плагини што се конфигурирани за автоматско ажурирање.",
+ "TaskUpdatePlugins": "Ажурирај Плагини",
+ "TaskRefreshPeopleDescription": "Ажурирај метадата за акери и директори во вашата медиска библиотека.",
+ "TaskRefreshPeople": "Ажурирајте ги Луѓето",
+ "TaskCleanLogsDescription": "Избриши лог фајлови постари од {0} денови.",
+ "TaskCleanLogs": "Избриши Директориум на Логови",
+ "TaskRefreshLibraryDescription": "Скенирајте ја вашата медиска библиотека за нови фајлови и ажурирај метадата.",
+ "TaskRefreshLibrary": "Скенирај Медиумска Библиотека",
+ "TaskRefreshChapterImagesDescription": "Создава тамбнеил за видеата шти имаат поглавја.",
+ "TaskCleanActivityLogDescription": "Избришува логови на активности постари од определеното време.",
+ "TaskCleanActivityLog": "Избриши Лог на Активности",
+ "External": "Надворешен"
}
diff --git a/Emby.Server.Implementations/Localization/Core/mr.json b/Emby.Server.Implementations/Localization/Core/mr.json
index 5aad4b0ed..b2227e454 100644
--- a/Emby.Server.Implementations/Localization/Core/mr.json
+++ b/Emby.Server.Implementations/Localization/Core/mr.json
@@ -100,5 +100,27 @@
"MixedContent": "मिश्रित सामग्री",
"LabelRunningTimeValue": "चालू काल: {0}",
"HeaderContinueWatching": "बघणे चालू ठेवा",
- "Default": "डीफॉल्ट"
+ "Default": "डीफॉल्ट",
+ "TaskKeyframeExtractorDescription": "अधिक अचूक HLS प्लेलिस्ट तयार करण्यासाठी व्हिडिओ फाइल्समधून कीफ्रेम काढते. हे कार्य दीर्घकाळ चालू शकते.",
+ "TaskKeyframeExtractor": "कीफ्रेम एक्स्ट्रॅक्टर",
+ "TaskOptimizeDatabaseDescription": "डेटाबेस कॉम्पॅक्ट करतो आणि मोकळी जागा कमी करतो. लायब्ररी स्कॅन केल्यावर किंवा डेटाबेस बदल सुचवणारे इतर बदल केल्यावर हे कार्य चालवल्याने कार्यप्रदर्शन सुधारू शकते.",
+ "TaskOptimizeDatabase": "डेटाबेस ऑप्टिमाइझ करा",
+ "TaskCleanLogsDescription": "{0} दिवसांपेक्षा जुन्या लॉग फाइल्स हटवा.",
+ "TaskCleanCacheDescription": "सिस्टमला यापुढे आवश्यक नसलेल्या कॅशे फाइल्स हटवा.",
+ "TaskCleanActivityLogDescription": "कॉन्फिगर केलेल्या वयापेक्षा जुन्या क्रियाकलाप लॉग एंट्री हटवा.",
+ "TaskCleanActivityLog": "क्रियाकलाप लॉग साफ करा",
+ "UserPolicyUpdatedWithName": "{0} साठी वापरकर्ता धोरण अपडेट केले गेले आहे",
+ "UserOfflineFromDevice": "{0} {1} वरून डिस्कनेक्ट झाला आहे",
+ "UserLockedOutWithName": "वापरकर्ता {0} लॉक केले गेले आहे",
+ "NotificationOptionUserLockedOut": "वापरकर्ता लॉक आउट",
+ "NameInstallFailed": "{0} स्थापना अयशस्वी",
+ "MessageServerConfigurationUpdated": "सर्व्हर कॉन्फिगरेशन अद्यतनित केले आहे",
+ "MessageNamedServerConfigurationUpdatedWithValue": "सर्व्हर कॉन्फिगरेशन विभाग {0} अद्यतनित केला गेला आहे",
+ "Inherit": "वारसा",
+ "Forced": "सक्ती केली आहे",
+ "FailedLoginAttemptWithUserName": "अयशस्वी लॉगिन {0} पासून प्रयत्न करा",
+ "External": "बाहेरचा",
+ "DeviceOnlineWithName": "{0} कनेक्ट झाले",
+ "DeviceOfflineWithName": "{0} डिस्कनेक्ट झाला आहे",
+ "AuthenticationSucceededWithUserName": "{0} यशस्वीरित्या प्रमाणीकृत"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json
index deb28970c..3d54a5a95 100644
--- a/Emby.Server.Implementations/Localization/Core/ms.json
+++ b/Emby.Server.Implementations/Localization/Core/ms.json
@@ -61,7 +61,7 @@
"NotificationOptionVideoPlayback": "Ulangmain video bermula",
"NotificationOptionVideoPlaybackStopped": "Ulangmain video dihentikan",
"Photos": "Gambar-gambar",
- "Playlists": "Senarai main",
+ "Playlists": "Senarai ulangmain",
"Plugin": "Plugin",
"PluginInstalledWithName": "{0} telah dipasang",
"PluginUninstalledWithName": "{0} telah dinyahpasang",
diff --git a/Emby.Server.Implementations/Localization/Core/my.json b/Emby.Server.Implementations/Localization/Core/my.json
index 418376c4e..198f7540c 100644
--- a/Emby.Server.Implementations/Localization/Core/my.json
+++ b/Emby.Server.Implementations/Localization/Core/my.json
@@ -1,123 +1,124 @@
{
"Default": "ပုံသေ",
"Collections": "စုစည်းမှုများ",
- "Channels": "ချန်နယ်များ",
+ "Channels": "တီဗွီလိုင်းများ",
"Books": "စာအုပ်များ",
"Artists": "အနုပညာရှင်များ",
- "Albums": "အခွေများ",
+ "Albums": "သီချင်းအခွေများ",
"TaskOptimizeDatabaseDescription": "ဒေတာဘေ့စ်ကို ကျစ်လစ်စေပြီး နေရာလွတ်များကို ဖြတ်တောက်ပေးသည်။ စာကြည့်တိုက်ကို စကင်န်ဖတ်ပြီးနောက် ဤလုပ်ငန်းကို လုပ်ဆောင်ခြင်း သို့မဟုတ် ဒေတာဘေ့စ်မွမ်းမံမှုများ စွမ်းဆောင်ရည်ကို မြှင့်တင်ပေးနိုင်သည်ဟု ရည်ညွှန်းသော အခြားပြောင်းလဲမှုများကို လုပ်ဆောင်ခြင်း။.",
- "TaskOptimizeDatabase": "ဒေတာဘေ့စ်ကို အကောင်းဆုံးဖြစ်အောင်လုပ်ပါ။",
+ "TaskOptimizeDatabase": "ဒေတာဘေ့စ်ကို အကောင်းဆုံးဖြစ်အောင်လုပ်ပါ",
"TaskDownloadMissingSubtitlesDescription": "မက်တာဒေတာ ဖွဲ့စည်းမှုပုံစံအပေါ် အခြေခံ၍ ပျောက်ဆုံးနေသော စာတန်းထိုးများအတွက် အင်တာနက်ကို ရှာဖွေသည်။",
- "TaskDownloadMissingSubtitles": "ပျောက်ဆုံးနေသော စာတန်းထိုးများကို ဒေါင်းလုဒ်လုပ်ပါ။",
+ "TaskDownloadMissingSubtitles": "ပျောက်ဆုံးနေသော စာတန်းထိုးများကို ဒေါင်းလုဒ်လုပ်ပါ",
"TaskRefreshChannelsDescription": "အင်တာနက်ချန်နယ်အချက်အလက်ကို ပြန်လည်စတင်သည်။",
- "TaskRefreshChannels": "ချန်နယ်များကို ပြန်လည်စတင်ပါ။",
+ "TaskRefreshChannels": "ချန်နယ်များကို ပြန်လည်စတင်ပါ",
"TaskCleanTranscodeDescription": "သက်တမ်း တစ်ရက်ထက်ပိုသော အသွင်ပြောင်းကုဒ်ဖိုင်များကို ဖျက်ပါ။",
- "TaskCleanTranscode": "Transcode လမ်းညွှန်ကို သန့်ရှင်းပါ။",
+ "TaskCleanTranscode": "Transcode လမ်းညွှန်ကို သန့်ရှင်းပါ",
"TaskUpdatePluginsDescription": "အလိုအလျောက် အပ်ဒိတ်လုပ်ရန် စီစဉ်ထားသော ပလပ်အင်များအတွက် အပ်ဒိတ်များကို ဒေါင်းလုဒ်လုပ်ပြီး ထည့်သွင်းပါ။",
- "TaskUpdatePlugins": "ပလပ်အင်များကို အပ်ဒိတ်လုပ်ပါ။",
+ "TaskUpdatePlugins": "ပလပ်အင်များကို အပ်ဒိတ်လုပ်ပါ",
"TaskRefreshPeopleDescription": "သင့်မီဒီယာစာကြည့်တိုက်ရှိ သရုပ်ဆောင်များနှင့် ဒါရိုက်တာများအတွက် မက်တာဒေတာကို အပ်ဒိတ်လုပ်ပါ။",
- "TaskRefreshPeople": "လူများကို ပြန်လည်ဆန်းသစ်ပါ။",
+ "TaskRefreshPeople": "လူများကို ပြန်လည်ဆန်းသစ်ပါ",
"TaskCleanLogsDescription": "{0} ရက်ထက်ပိုသော မှတ်တမ်းဖိုင်များကို ဖျက်သည်။",
- "TaskCleanLogs": "မှတ်တမ်းလမ်းညွှန်ကို သန့်ရှင်းပါ။",
- "TaskRefreshLibraryDescription": "ဖိုင်အသစ်များအတွက် သင့်မီဒီယာဒစ်ဂျစ်တိုက်ကို စကင်န်ဖတ်ပြီး မက်တာဒေတာကို ပြန်လည်စတင်ပါ။",
- "TaskRefreshLibrary": "မီဒီယာစာကြည့်တိုက်ကို စကင်န်ဖတ်ပါ။",
+ "TaskCleanLogs": "မှတ်တမ်းလမ်းညွှန်ကို သန့်ရှင်းပါ",
+ "TaskRefreshLibraryDescription": "သင့်မီဒီယာဒစ်ဂျစ်တိုက်ကို ဖိုင်အသစ်များရှိမရှိ စကင်န်ဖတ်ပြီး ဖိုင်ရဲ့အကြောင်းအရာများ ကို ပြန်ပြုပြင်မွမ်းမံပါ။",
+ "TaskRefreshLibrary": "မီဒီယာစာကြည့်တိုက်ကို စကင်န်ဖတ်ပါ",
"TaskRefreshChapterImagesDescription": "အခန်းများပါရှိသော ဗီဒီယိုများအတွက် ပုံသေးများကို ဖန်တီးပါ။",
- "TaskRefreshChapterImages": "အခန်းပုံများကို ထုတ်ယူပါ။",
+ "TaskRefreshChapterImages": "အခန်းတစ်ခုစီ ပုံများကို ထုတ်ယူပါ",
"TaskCleanCacheDescription": "စနစ်မှ မလိုအပ်တော့သော ကက်ရှ်ဖိုင်များကို ဖျက်ပါ။.",
- "TaskCleanCache": "Cache Directory ကို ရှင်းပါ။",
+ "TaskCleanCache": "Cache Directory ကို ရှင်းပါ",
"TaskCleanActivityLogDescription": "စီစဉ်သတ်မှတ်ထားသော အသက်ထက် ပိုကြီးသော လုပ်ဆောင်ချက်မှတ်တမ်းများကို ဖျက်ပါ။",
- "TaskCleanActivityLog": "လုပ်ဆောင်ချက်မှတ်တမ်းကို ရှင်းလင်းပါ။",
- "TasksChannelsCategory": "အင်တာနက်ချန်နယ်များ",
+ "TaskCleanActivityLog": "လုပ်ဆောင်ချက်မှတ်တမ်းကို ရှင်းလင်းပါ",
+ "TasksChannelsCategory": "အင်တာနက် ချန်နယ်လိုင်းများ",
"TasksApplicationCategory": "အပလီကေးရှင်း",
- "TasksLibraryCategory": "စာကြည့်တိုက်",
- "TasksMaintenanceCategory": "ထိန်းသိမ်းခြင်း",
+ "TasksLibraryCategory": "မီဒီယာတိုက်",
+ "TasksMaintenanceCategory": "ပြုပြင် ထိန်းသိမ်းခြင်း",
"VersionNumber": "ဗားရှင်း {0}",
"ValueSpecialEpisodeName": "အထူး- {0}",
- "ValueHasBeenAddedToLibrary": "{0} ကို သင့်မီဒီယာဒစ်ဂျစ်တိုက်သို့ ပေါင်းထည့်လိုက်ပါပြီ။",
+ "ValueHasBeenAddedToLibrary": "{0} ကို သင့်မီဒီယာဒစ်ဂျစ်တိုက်သို့ ပေါင်းထည့်လိုက်ပါပြီ",
"UserStoppedPlayingItemWithValues": "{0} သည် {1} ကို {2} တွင် ဖွင့်ပြီးပါပြီ",
"UserStartedPlayingItemWithValues": "{0} သည် {1} ကို {2} တွင် ပြသနေသည်",
"UserPolicyUpdatedWithName": "{0} အတွက် အသုံးပြုသူမူဝါဒကို အပ်ဒိတ်လုပ်ပြီးပါပြီ",
"UserPasswordChangedWithName": "အသုံးပြုသူ {0} အတွက် စကားဝှက်ကို ပြောင်းထားသည်",
"UserOnlineFromDevice": "{0} သည် {1} မှ အွန်လိုင်းဖြစ်သည်",
"UserOfflineFromDevice": "{0} သည် {1} မှ ချိတ်ဆက်မှုပြတ်တောက်သွားသည်",
- "UserLockedOutWithName": "အသုံးပြုသူ {0} အား လော့ခ်ချထားသည်။",
+ "UserLockedOutWithName": "အသုံးပြုသူ {0} အား လော့ခ်ချထားသည်",
"UserDownloadingItemWithValues": "{0} သည် {1} ကို ဒေါင်းလုဒ်လုပ်နေသည်",
- "UserDeletedWithName": "အသုံးပြုသူ {0} ကို ဖျက်လိုက်ပါပြီ။",
- "UserCreatedWithName": "အသုံးပြုသူ {0} ကို ဖန်တီးပြီးပါပြီ။",
+ "UserDeletedWithName": "အသုံးပြုသူ {0} ကို ဖျက်လိုက်ပါပြီ",
+ "UserCreatedWithName": "အသုံးပြုသူ {0} ကို ဖန်တီးပြီးပါပြီ",
"User": "အသုံးပြုသူ",
"Undefined": "သတ်မှတ်မထားသော",
- "TvShows": "တီဗီရှိုးများ",
+ "TvShows": "တီဗီ ဇာတ်လမ်းတွဲများ",
"System": "စနစ်",
"Sync": "ထပ်တူကျသည်။",
- "SubtitleDownloadFailureFromForItem": "စာတန်းထိုးများကို {1} အတွက် {0} မှ ဒေါင်းလုဒ်လုပ်၍ မရပါ",
- "StartupEmbyServerIsLoading": "Jellyfin ဆာဗာကို ဖွင့်နေပါသည်။ ခဏနေ ထပ်စမ်းကြည့်ပါ။",
+ "SubtitleDownloadFailureFromForItem": "{1} အတွက် {0} မှ စာတန်းထိုးများ ဒေါင်းလုဒ်လုပ်ခြင်း မအောင်မြင်ပါ",
+ "StartupEmbyServerIsLoading": "Jellyfin ဆာဗာကို အသင့်ပြင်နေပါသည်။ ခဏနေ ထပ်စမ်းကြည့်ပါ။",
"Songs": "သီချင်းများ",
- "Shows": "ရှိုးပွဲ",
- "ServerNameNeedsToBeRestarted": "{0} ကို ပြန်လည်စတင်ရန် လိုအပ်သည်။",
- "ScheduledTaskStartedWithName": "{0} စတင်ခဲ့သည်။",
- "ScheduledTaskFailedWithName": "{0} မအောင်မြင်ပါ။",
+ "Shows": "ဇာတ်လမ်းတွဲများ",
+ "ServerNameNeedsToBeRestarted": "{0} ကို ပြန်လည်စတင်ရန် လိုအပ်သည်",
+ "ScheduledTaskStartedWithName": "{0} စတင်ခဲ့သည်",
+ "ScheduledTaskFailedWithName": "{0} မအောင်မြင်ပါ",
"ProviderValue": "ဝန်ဆောင်မှုပေးသူ- {0}",
- "PluginUpdatedWithName": "{0} ကို အပ်ဒိတ်လုပ်ထားသည်။",
- "PluginUninstalledWithName": "{0} ကို ဖြုတ်လိုက်ပါပြီ။",
- "PluginInstalledWithName": "{0} ကို ထည့်သွင်းခဲ့သည်။",
+ "PluginUpdatedWithName": "ပလပ်ခ်အင် {0} ကို အပ်ဒိတ်လုပ်ထားသည်",
+ "PluginUninstalledWithName": "ပလပ်ခ်အင် {0} ကို ဖြုတ်လိုက်ပါပြီ",
+ "PluginInstalledWithName": "ပလပ်ခ်အင် {0} ကို ထည့်သွင်းခဲ့သည်",
"Plugin": "ပလပ်အင်",
"Playlists": "အစီအစဉ်များ",
"Photos": "ဓာတ်ပုံများ",
- "NotificationOptionVideoPlaybackStopped": "ဗီဒီယိုပြန်ဖွင့်ခြင်းကို ရပ်သွားသည်။",
- "NotificationOptionVideoPlayback": "ဗီဒီယိုဖွင့်ခြင်း စတင်ပါပြီ။",
- "NotificationOptionUserLockedOut": "အသုံးပြုသူ ထွက်သွားသည်။",
+ "NotificationOptionVideoPlaybackStopped": "ဗီဒီယိုဖွင့်ခြင်း ရပ်သွားသည်",
+ "NotificationOptionVideoPlayback": "ဗီဒီယိုဖွင့်ခြင်း စတင်ပါပြီ",
+ "NotificationOptionUserLockedOut": "အသုံးပြုသူ ဝင်ရန် တားမြစ်ခံရသည်",
"NotificationOptionTaskFailed": "စီစဉ်ထားသော အလုပ်ပျက်ကွက်",
- "NotificationOptionServerRestartRequired": "ဆာဗာ ပြန်လည်စတင်ရန် လိုအပ်သည်။",
- "NotificationOptionPluginUpdateInstalled": "ပလပ်အင် အပ်ဒိတ် ထည့်သွင်းပြီးပါပြီ။",
- "NotificationOptionPluginUninstalled": "ပလပ်အင်ကို ဖြုတ်လိုက်ပါပြီ။",
- "NotificationOptionPluginInstalled": "ပလပ်အင် ထည့်သွင်းထားသည်။",
- "NotificationOptionPluginError": "ပလပ်အင် ချို့ယွင်းခြင်း။",
- "NotificationOptionNewLibraryContent": "အကြောင်းအရာအသစ် ထပ်ထည့်ထားပါတယ်။",
- "NotificationOptionInstallationFailed": "တပ်ဆင်မှု မအောင်မြင်ပါ။",
- "NotificationOptionCameraImageUploaded": "ကင်မရာပုံ အပ်လုဒ်လုပ်ထားသည်။",
- "NotificationOptionAudioPlaybackStopped": "အသံပြန်ဖွင့်ခြင်းကို ရပ်သွားသည်။",
- "NotificationOptionAudioPlayback": "အသံပြန်ဖွင့်ခြင်း စတင်ပါပြီ။",
- "NotificationOptionApplicationUpdateInstalled": "အပလီကေးရှင်း အပ်ဒိတ်ကို ထည့်သွင်းထားသည်။",
- "NotificationOptionApplicationUpdateAvailable": "အပလီကေးရှင်း အပ်ဒိတ် ရနိုင်ပါပြီ။",
- "NewVersionIsAvailable": "Jellyfin Server ၏ ဗားရှင်းအသစ်ကို ဒေါင်းလုဒ်လုပ်နိုင်ပါသည်။",
- "NameSeasonUnknown": "အမည််မသိ ဇာတ်လမ်းတွဲ",
- "NameSeasonNumber": "ဇာတ်လမ်းတွဲ {0}",
- "NameInstallFailed": "{0} ထည့်သွင်းမှု မအောင်မြင်ပါ။",
+ "NotificationOptionServerRestartRequired": "ဆာဗာ ပြန်လည်စတင်ရန် လိုအပ်သည်",
+ "NotificationOptionPluginUpdateInstalled": "ပလပ်အင် အပ်ဒိတ် ထည့်သွင်းပြီးပါပြီ",
+ "NotificationOptionPluginUninstalled": "ပလပ်အင်ကို ဖြုတ်လိုက်ပါပြီ",
+ "NotificationOptionPluginInstalled": "ပလပ်အင် ထည့်သွင်းထားသည်",
+ "NotificationOptionPluginError": "ပလပ်အင် ချို့ယွင်းခြင်း",
+ "NotificationOptionNewLibraryContent": "အသစ်များ ထပ်ထည့်ထားပါတယ်",
+ "NotificationOptionInstallationFailed": "ထည့်သွင်းမှု မအောင်မြင်ပါ",
+ "NotificationOptionCameraImageUploaded": "ကင်မရာမှ ဓာတ်ပုံ အပ်လုဒ် ပြီးပါပြီ",
+ "NotificationOptionAudioPlaybackStopped": "အသံဖိုင်ဖွင့်ခြင်း ရပ်သွားသည်",
+ "NotificationOptionAudioPlayback": "အသံဖွင့်ခြင်း စတင်ပါပြီ",
+ "NotificationOptionApplicationUpdateInstalled": "အပလီကေးရှင်း အပ်ဒိတ်ကို ထည့်သွင်းထားသည်",
+ "NotificationOptionApplicationUpdateAvailable": "အပလီကေးရှင်း အပ်ဒိတ် ရနိုင်ပါပြီ",
+ "NewVersionIsAvailable": "Jellyfin Server ၏ ဗားရှင်းအသစ်ကို ဒေါင်းလုဒ်လုပ်နိုင်ပါပြီ။",
+ "NameSeasonUnknown": "ဇာတ်လမ်းတွဲ အပိုင်းမသိ",
+ "NameSeasonNumber": "ဇာတ်လမ်းတွဲ အပိုင်း {0}",
+ "NameInstallFailed": "{0} ထည့်သွင်းမှု မအောင်မြင်ပါ",
"MusicVideos": "ဂီတဗီဒီယိုများ",
"Music": "တေးဂီတ",
"Movies": "ရုပ်ရှင်များ",
"MixedContent": "ရောနှောပါဝင်မှု",
- "MessageServerConfigurationUpdated": "ဆာဗာဖွဲ့စည်းပုံကို အပ်ဒိတ်လုပ်ပြီးပါပြီ။",
- "MessageNamedServerConfigurationUpdatedWithValue": "ဆာဗာဖွဲ့စည်းပုံကဏ္ဍ {0} ကို အပ်ဒိတ်လုပ်ပြီးပါပြီ။",
+ "MessageServerConfigurationUpdated": "ဆာဗာဖွဲ့စည်းပုံကို အပ်ဒိတ်လုပ်ပြီးပါပြီ",
+ "MessageNamedServerConfigurationUpdatedWithValue": "ဆာဗာဖွဲ့စည်းပုံကဏ္ဍ {0} ကို အပ်ဒိတ်လုပ်ပြီးပါပြီ",
"MessageApplicationUpdatedTo": "Jellyfin ဆာဗာကို {0} သို့ အပ်ဒိတ်လုပ်ထားသည်",
- "MessageApplicationUpdated": "Jellyfin ဆာဗာကို အပ်ဒိတ်လုပ်ပြီးပါပြီ။",
+ "MessageApplicationUpdated": "Jellyfin ဆာဗာကို အပ်ဒိတ်လုပ်ပြီးပါပြီ",
"Latest": "နောက်ဆုံး",
- "LabelRunningTimeValue": "လည်ပတ်ချိန်- {0}",
+ "LabelRunningTimeValue": "ကြာချိန် - {0}",
"LabelIpAddressValue": "IP လိပ်စာ- {0}",
- "ItemRemovedWithName": "{0} ကို ဒစ်ဂျစ်တိုက်မှ ဖယ်ရှားခဲ့သည်။",
- "ItemAddedWithName": "{0} ကို စာကြည့်တိုက်သို့ ထည့်ထားသည်။",
- "Inherit": "ဆက်လက် လုပ်ဆောင်သည်။",
- "HomeVideos": "ပင်မဗီဒီယိုများ",
+ "ItemRemovedWithName": "{0} ကို ဒစ်ဂျစ်တိုက်မှ ဖယ်ရှားခဲ့သည်",
+ "ItemAddedWithName": "{0} ကို စာကြည့်တိုက်သို့ ထည့်ထားသည်",
+ "Inherit": "ဆက်ခံ၍ လုပ်ဆောင်သည်",
+ "HomeVideos": "ကိုယ်တိုင်ရိုက် ဗီဒီယိုများ",
"HeaderRecordingGroups": "အသံဖမ်းအဖွဲ့များ",
"HeaderNextUp": "နောက်ထပ်",
- "HeaderLiveTV": "Live TV",
+ "HeaderLiveTV": "တီဗွီတိုက်ရိုက်",
"HeaderFavoriteSongs": "အကြိုက်ဆုံးသီချင်းများ",
- "HeaderFavoriteShows": "အကြိုက်ဆုံးရှိုးများ",
- "HeaderFavoriteEpisodes": "အကြိုက်ဆုံးအပိုင်းများ",
+ "HeaderFavoriteShows": "အကြိုက်ဆုံး ဇာတ်လမ်းတွဲများ",
+ "HeaderFavoriteEpisodes": "အကြိုက်ဆုံး ဇာတ်လမ်းအပိုင်းများ",
"HeaderFavoriteArtists": "အကြိုက်ဆုံးအနုပညာရှင်များ",
"HeaderFavoriteAlbums": "အကြိုက်ဆုံး အယ်လ်ဘမ်များ",
- "HeaderContinueWatching": "ဆက်လက်ကြည့်ရှုပါ။",
+ "HeaderContinueWatching": "ဆက်လက်ကြည့်ရှုပါ",
"HeaderAlbumArtists": "အယ်လ်ဘမ်အနုပညာရှင်များ",
"Genres": "အမျိုးအစားများ",
"Forced": "အတင်းအကြပ်",
- "Folders": "ဖိုဒါများ",
+ "Folders": "ဖိုလ်ဒါများ",
"Favorites": "အကြိုက်ဆုံးများ",
"FailedLoginAttemptWithUserName": "{0} မှ အကောင့်ဝင်ရန် မအောင်မြင်ပါ",
- "DeviceOnlineWithName": "{0} ကို ချိတ်ဆက်ထားသည်။",
- "DeviceOfflineWithName": "{0} နှင့် အဆက်ပြတ်သွားပါပြီ။",
+ "DeviceOnlineWithName": "{0} ကို ချိတ်ဆက်ထားသည်",
+ "DeviceOfflineWithName": "{0} နှင့် အဆက်ပြတ်သွားပါပြီ",
"ChapterNameValue": "အခန်း {0}",
- "CameraImageUploadedFrom": "ကင်မရာပုံအသစ်ကို {0} မှ အပ်လုဒ်လုပ်ထားသည်",
- "AuthenticationSucceededWithUserName": "{0} စစ်မှန်ကြောင်း အောင်မြင်စွာ အတည်ပြုပြီးပါပြီ။",
+ "CameraImageUploadedFrom": "ကင်မရာပုံအသစ်ကို {0} မှ ထည့်သွင်းလိုက်သည်",
+ "AuthenticationSucceededWithUserName": "{0} အောင်မြင်စွာ စစ်မှန်ကြောင်း အတည်ပြုပြီးပါပြီ",
"Application": "အပလီကေးရှင်း",
- "AppDeviceValues": "အက်ပ်- {0}၊ စက်- {1}"
+ "AppDeviceValues": "အက်ပ်- {0}၊ စက်- {1}",
+ "External": "ပြင်ပ"
}
diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json
index 317bdcfcb..77ee46a4f 100644
--- a/Emby.Server.Implementations/Localization/Core/nb.json
+++ b/Emby.Server.Implementations/Localization/Core/nb.json
@@ -120,5 +120,8 @@
"Default": "Standard",
"TaskCleanActivityLogDescription": "Sletter oppføringer i aktivitetsloggen som er eldre enn den konfigurerte alderen.",
"TaskOptimizeDatabase": "Optimiser database",
- "TaskOptimizeDatabaseDescription": "Komprimerer database og frigjør plass. Denne prosessen kan forbedre ytelsen etter skanning av bibliotek eller andre handlinger som fører til databaseendringer."
+ "TaskOptimizeDatabaseDescription": "Komprimerer database og frigjør plass. Denne prosessen kan forbedre ytelsen etter skanning av bibliotek eller andre handlinger som fører til databaseendringer.",
+ "TaskKeyframeExtractorDescription": "Trekker ut nøkkelbilder fra videofiler for å skape mere nøyaktige HLS-spillelister. Denne oppgaven kan ta lang tid.",
+ "TaskKeyframeExtractor": "Nøkkelbilde-uttrekker",
+ "External": "Ekstern"
}
diff --git a/Emby.Server.Implementations/Localization/Core/pt-PT.json b/Emby.Server.Implementations/Localization/Core/pt-PT.json
index 8870de168..7047f1c28 100644
--- a/Emby.Server.Implementations/Localization/Core/pt-PT.json
+++ b/Emby.Server.Implementations/Localization/Core/pt-PT.json
@@ -8,15 +8,15 @@
"CameraImageUploadedFrom": "Uma nova imagem de câmara foi enviada a partir de {0}",
"Channels": "Canais",
"ChapterNameValue": "Capítulo {0}",
- "Collections": "Coleções",
+ "Collections": "Colecções",
"DeviceOfflineWithName": "{0} desligou-se",
"DeviceOnlineWithName": "{0} ligou-se",
"FailedLoginAttemptWithUserName": "Tentativa de login falhada a partir de {0}",
"Favorites": "Favoritos",
"Folders": "Pastas",
"Genres": "Géneros",
- "HeaderAlbumArtists": "Álbum do Artista",
- "HeaderContinueWatching": "Continuar a Ver",
+ "HeaderAlbumArtists": "Artistas do álbum",
+ "HeaderContinueWatching": "Continuar a ver",
"HeaderFavoriteAlbums": "Álbuns Favoritos",
"HeaderFavoriteArtists": "Artistas Favoritos",
"HeaderFavoriteEpisodes": "Episódios Favoritos",
@@ -120,5 +120,8 @@
"Forced": "Forçado",
"Default": "Padrão",
"TaskOptimizeDatabaseDescription": "Base de dados compacta e corta espaço livre. A execução desta tarefa depois de digitalizar a biblioteca ou de fazer outras alterações que impliquem modificações na base de dados pode melhorar o desempenho.",
- "TaskOptimizeDatabase": "Otimizar base de dados"
+ "TaskOptimizeDatabase": "Otimizar base de dados",
+ "TaskKeyframeExtractorDescription": "Extrai quadros-chave de ficheiros de video para criar listas de reprodução HLS mais precisas. Esta tarefa pode demorar algum tempo.",
+ "TaskKeyframeExtractor": "Extrator de Quadros-chave",
+ "External": "Externo"
}
diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json
index a9dbd53ea..c2c77ccab 100644
--- a/Emby.Server.Implementations/Localization/Core/pt.json
+++ b/Emby.Server.Implementations/Localization/Core/pt.json
@@ -1,5 +1,5 @@
{
- "HeaderLiveTV": "TV Ao Vivo",
+ "HeaderLiveTV": "TV Em Direto",
"Collections": "Coleções",
"Books": "Livros",
"Artists": "Artistas",
@@ -10,9 +10,9 @@
"HeaderFavoriteAlbums": "Álbuns Favoritos",
"HeaderFavoriteEpisodes": "Episódios Favoritos",
"HeaderFavoriteShows": "Séries Favoritas",
- "HeaderContinueWatching": "Continuar assistindo",
+ "HeaderContinueWatching": "Continuar a ver",
"HeaderAlbumArtists": "Artistas do Álbum",
- "Genres": "Gêneros",
+ "Genres": "Géneros",
"Folders": "Diretórios",
"Favorites": "Favoritos",
"Channels": "Canais",
@@ -74,7 +74,7 @@
"ItemRemovedWithName": "{0} foi removido da biblioteca",
"ItemAddedWithName": "{0} foi adicionado à biblioteca",
"Inherit": "Herdar",
- "HomeVideos": "Vídeos principais",
+ "HomeVideos": "Vídeos Caseiros",
"HeaderRecordingGroups": "Grupos de Gravação",
"ValueSpecialEpisodeName": "Episódio Especial - {0}",
"Sync": "Sincronização",
@@ -83,14 +83,14 @@
"Playlists": "Listas de Reprodução",
"Photos": "Fotografias",
"Movies": "Filmes",
- "FailedLoginAttemptWithUserName": "Tentativa falha de login a partir de {0}",
- "DeviceOnlineWithName": "{0} está conectado",
- "DeviceOfflineWithName": "{0} desconectou-se",
+ "FailedLoginAttemptWithUserName": "Tentativa de início de sessão falhada a partir de {0}",
+ "DeviceOnlineWithName": "{0} está ligado",
+ "DeviceOfflineWithName": "{0} desligou-se",
"ChapterNameValue": "Capítulo {0}",
"CameraImageUploadedFrom": "Uma nova imagem da câmara foi enviada a partir de {0}",
"AuthenticationSucceededWithUserName": "{0} autenticado com sucesso",
- "Application": "Aplicativo",
- "AppDeviceValues": "Aplicativo {0}, Dispositivo: {1}",
+ "Application": "Aplicação",
+ "AppDeviceValues": "Aplicação: {0}, Dispositivo: {1}",
"TaskCleanCache": "Limpar Diretório de Cache",
"TasksApplicationCategory": "Aplicativo",
"TasksLibraryCategory": "Biblioteca",
@@ -119,5 +119,6 @@
"Default": "Predefinição",
"TaskCleanActivityLogDescription": "Apaga itens no registro com idade acima do que é configurado.",
"TaskOptimizeDatabase": "Otimizar base de dados",
- "TaskOptimizeDatabaseDescription": "Base de dados compacta e corta espaço livre. A execução desta tarefa depois de digitalizar a biblioteca ou de fazer outras alterações que impliquem modificações na base de dados pode melhorar o desempenho."
+ "TaskOptimizeDatabaseDescription": "Base de dados compacta e corta espaço livre. A execução desta tarefa depois de digitalizar a biblioteca ou de fazer outras alterações que impliquem modificações na base de dados pode melhorar o desempenho.",
+ "External": "Externo"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json
index f489597a1..ea9a82d2b 100644
--- a/Emby.Server.Implementations/Localization/Core/ru.json
+++ b/Emby.Server.Implementations/Localization/Core/ru.json
@@ -31,12 +31,12 @@
"ItemRemovedWithName": "{0} - изъято из медиатеки",
"LabelIpAddressValue": "IP-адрес: {0}",
"LabelRunningTimeValue": "Длительность: {0}",
- "Latest": "Крайнее",
+ "Latest": "Новое",
"MessageApplicationUpdated": "Jellyfin Server был обновлён",
"MessageApplicationUpdatedTo": "Jellyfin Server был обновлён до {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Конфигурация сервера (раздел {0}) была обновлена",
"MessageServerConfigurationUpdated": "Конфигурация сервера была обновлена",
- "MixedContent": "Смешанное содержимое",
+ "MixedContent": "Смешанное содержание",
"Movies": "Кино",
"Music": "Музыка",
"MusicVideos": "Муз. видео",
diff --git a/Emby.Server.Implementations/Localization/Core/sl-SI.json b/Emby.Server.Implementations/Localization/Core/sl-SI.json
index a6fcbd3e2..d845accac 100644
--- a/Emby.Server.Implementations/Localization/Core/sl-SI.json
+++ b/Emby.Server.Implementations/Localization/Core/sl-SI.json
@@ -90,7 +90,7 @@
"UserStartedPlayingItemWithValues": "{0} predvaja {1} na {2}",
"UserStoppedPlayingItemWithValues": "{0} je nehal predvajati {1} na {2}",
"ValueHasBeenAddedToLibrary": "{0} je bil dodan vaši knjižnici",
- "ValueSpecialEpisodeName": "Bonus - {0}",
+ "ValueSpecialEpisodeName": "Posebna epizoda - {0}",
"VersionNumber": "Različica {0}",
"TaskDownloadMissingSubtitles": "Prenesi manjkajoče podnapise",
"TaskRefreshChannelsDescription": "Osveži podatke spletnih kanalov.",
@@ -120,5 +120,8 @@
"Forced": "Prisilno",
"Default": "Privzeto",
"TaskOptimizeDatabaseDescription": "Stisne bazo podatkov in uredi prazen prostor. Zagon tega opravila po iskanju predstavnosti ali drugih spremembah ki vplivajo na bazo podatkov lahko izboljša hitrost delovanja.",
- "TaskOptimizeDatabase": "Optimiziraj bazo podatkov"
+ "TaskOptimizeDatabase": "Optimiziraj bazo podatkov",
+ "TaskKeyframeExtractor": "Ekstraktor ključnih sličic",
+ "External": "Zunanji",
+ "TaskKeyframeExtractorDescription": "Iz video datoteke Izvleče ključne sličice, da ustvari bolj natančne sezname predvajanja HLS. Proces lahko traja dolgo časa."
}
diff --git a/Emby.Server.Implementations/Localization/Core/sr.json b/Emby.Server.Implementations/Localization/Core/sr.json
index 72e125dfe..1be8867f4 100644
--- a/Emby.Server.Implementations/Localization/Core/sr.json
+++ b/Emby.Server.Implementations/Localization/Core/sr.json
@@ -86,7 +86,7 @@
"Channels": "Канали",
"CameraImageUploadedFrom": "Нова фотографија је учитана са {0}",
"Books": "Књиге",
- "AuthenticationSucceededWithUserName": "{0} Успешна аутентикација",
+ "AuthenticationSucceededWithUserName": "{0} Успешна аутентификација",
"Artists": "Извођачи",
"Application": "Апликација",
"AppDeviceValues": "Апликација: {0}, Уређај: {1}",
@@ -118,6 +118,9 @@
"Undefined": "Недефинисано",
"Forced": "Принудно",
"Default": "Подразумевано",
- "TaskOptimizeDatabase": "Оптимизуј датабазу",
- "TaskOptimizeDatabaseDescription": "Сажима базу података и скраћује слободан простор. Покретање овог задатка након скенирања библиотеке или других промена које подразумевају измене базе података које могу побољшати перформансе."
+ "TaskOptimizeDatabase": "Оптимизуј банку података",
+ "TaskOptimizeDatabaseDescription": "Сажима базу података и скраћује слободан простор. Покретање овог задатка након скенирања библиотеке или других промена које подразумевају измене базе података које могу побољшати перформансе.",
+ "External": "Спољно",
+ "TaskKeyframeExtractorDescription": "Екстрактује кљулне сличице из видео датотека да би креирао више преицзну HLS плеј-листу. Овај задатак може да потраје дуже време.",
+ "TaskKeyframeExtractor": "Екстрактор кључних сличица"
}
diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json
index 2b9f9e232..af5db1976 100644
--- a/Emby.Server.Implementations/Localization/Core/sv.json
+++ b/Emby.Server.Implementations/Localization/Core/sv.json
@@ -9,20 +9,20 @@
"Channels": "Kanaler",
"ChapterNameValue": "Kapitel {0}",
"Collections": "Samlingar",
- "DeviceOfflineWithName": "{0} har kopplat ner",
+ "DeviceOfflineWithName": "{0} har avbrutit uppkopplingen",
"DeviceOnlineWithName": "{0} är ansluten",
"FailedLoginAttemptWithUserName": "Misslyckat inloggningsförsök från {0}",
"Favorites": "Favoriter",
"Folders": "Mappar",
"Genres": "Genrer",
- "HeaderAlbumArtists": "Albumsartister",
- "HeaderContinueWatching": "Fortsätt kolla på",
+ "HeaderAlbumArtists": "Albumartister",
+ "HeaderContinueWatching": "Fortsätt titta på",
"HeaderFavoriteAlbums": "Favoritalbum",
"HeaderFavoriteArtists": "Favoritartister",
"HeaderFavoriteEpisodes": "Favoritavsnitt",
"HeaderFavoriteShows": "Favoritserier",
"HeaderFavoriteSongs": "Favoritlåtar",
- "HeaderLiveTV": "Live-TV",
+ "HeaderLiveTV": "Direktsänd TV",
"HeaderNextUp": "Nästa",
"HeaderRecordingGroups": "Inspelningsgrupper",
"HomeVideos": "Hemmavideor",
@@ -95,33 +95,33 @@
"TaskDownloadMissingSubtitlesDescription": "Söker på internet efter saknade undertexter baserat på metadata-konfiguration.",
"TaskDownloadMissingSubtitles": "Ladda ner saknade undertexter",
"TaskRefreshChannelsDescription": "Uppdaterar information för internetkanaler.",
- "TaskRefreshChannels": "Uppdatera Kanaler",
+ "TaskRefreshChannels": "Uppdatera kanaler",
"TaskCleanTranscodeDescription": "Raderar omkodningsfiler äldre än en dag.",
- "TaskCleanTranscode": "Rensa Omkodningskatalog",
- "TaskUpdatePluginsDescription": "Laddar ned och installerar uppdateringar till insticksprogram som är konfigurerade att uppdateras automatiskt.",
- "TaskUpdatePlugins": "Uppdatera Insticksprogram",
+ "TaskCleanTranscode": "Rensa omkodningskatalog",
+ "TaskUpdatePluginsDescription": "Laddar ned och installerar uppdateringar till tilläggsprogram som är konfigurerade att uppdateras automatiskt.",
+ "TaskUpdatePlugins": "Uppdatera tilläggsprogram",
"TaskRefreshPeopleDescription": "Uppdaterar metadata för skådespelare och regissörer i ditt mediabibliotek.",
"TaskCleanLogsDescription": "Raderar loggfiler som är mer än {0} dagar gamla.",
- "TaskCleanLogs": "Rensa Loggkatalog",
- "TaskRefreshLibraryDescription": "Scannar ditt mediabibliotek efter nya filer och förnyar metadata.",
- "TaskRefreshLibrary": "Scanna Mediabibliotek",
+ "TaskCleanLogs": "Rensa loggkatalog",
+ "TaskRefreshLibraryDescription": "Skannar ditt mediabibliotek efter nya filer och uppdaterar metadata.",
+ "TaskRefreshLibrary": "Skanna mediabibliotek",
"TaskRefreshChapterImagesDescription": "Skapa miniatyrbilder för videor med kapitel.",
- "TaskRefreshChapterImages": "Extrahera Kapitelbilder",
+ "TaskRefreshChapterImages": "Extrahera kapitelbilder",
"TaskCleanCacheDescription": "Radera cachade filer som systemet inte längre behöver.",
- "TaskCleanCache": "Rensa Cachekatalog",
+ "TaskCleanCache": "Rensa cachekatalog",
"TasksChannelsCategory": "Internetkanaler",
"TasksApplicationCategory": "Applikation",
"TasksLibraryCategory": "Bibliotek",
"TasksMaintenanceCategory": "Underhåll",
- "TaskRefreshPeople": "Uppdatera Personer",
+ "TaskRefreshPeople": "Uppdatera personer",
"TaskCleanActivityLogDescription": "Radera aktivitetslogginlägg äldre än konfigurerad ålder.",
- "TaskCleanActivityLog": "Rensa Aktivitetslogg",
- "Undefined": "odefinierad",
+ "TaskCleanActivityLog": "Rensa aktivitetslogg",
+ "Undefined": "Odefinierad",
"Forced": "Tvingad",
"Default": "Standard",
- "TaskOptimizeDatabase": "Optimera Databasen",
- "TaskOptimizeDatabaseDescription": "Komprimerar databasen och trunkerar ledigt utrymme. Prestandan kan förbättras genom att köra denna task efter att du har skannat biblioteket eller gjort andra förändringar som indikerar att databasen har modifierats.",
- "TaskKeyframeExtractorDescription": "Exporterar keyframes från videofiler för att skapa mer exakta HLS-spellistor. Denna rutin kan ta lång tid.",
- "TaskKeyframeExtractor": "Keyframe-Extraktor",
+ "TaskOptimizeDatabase": "Optimera databasen",
+ "TaskOptimizeDatabaseDescription": "Komprimerar databasen och trunkerar ledigt utrymme. Prestandan kan förbättras genom att köra denna aktivitet efter att du har skannat biblioteket eller gjort andra förändringar som indikerar att databasen har modifierats.",
+ "TaskKeyframeExtractorDescription": "Exporterar nyckelbildrutor från videofiler för att skapa mer exakta HLS-spellistor. Denna rutin kan ta lång tid.",
+ "TaskKeyframeExtractor": "Extraktor för nyckelbildrutor",
"External": "Extern"
}
diff --git a/Emby.Server.Implementations/Localization/Core/th.json b/Emby.Server.Implementations/Localization/Core/th.json
index bed67fa4f..9407a7b92 100644
--- a/Emby.Server.Implementations/Localization/Core/th.json
+++ b/Emby.Server.Implementations/Localization/Core/th.json
@@ -119,5 +119,6 @@
"Undefined": "ไม่ได้กำหนด",
"Forced": "บังคับใช้",
"TaskOptimizeDatabase": "ปรับปรุงประสิทธิภาพฐานข้อมูล",
- "TaskOptimizeDatabaseDescription": "ลดขนาดการจัดเก็บฐานข้อมูล ใช้งานคำสั่งนี้หลังจากสแกนไลบรารีหรือหลังจากการเปลี่ยนแปลงฐานข้อมูล อาจจะทำให้ระบบทำงานเร็วขึ้น"
+ "TaskOptimizeDatabaseDescription": "ลดขนาดการจัดเก็บฐานข้อมูล ใช้งานคำสั่งนี้หลังจากสแกนไลบรารีหรือหลังจากการเปลี่ยนแปลงฐานข้อมูล อาจจะทำให้ระบบทำงานเร็วขึ้น",
+ "External": "ภายนอก"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ug.json b/Emby.Server.Implementations/Localization/Core/ug.json
index aea93c7fa..0bcbffb41 100644
--- a/Emby.Server.Implementations/Localization/Core/ug.json
+++ b/Emby.Server.Implementations/Localization/Core/ug.json
@@ -3,7 +3,122 @@
"Channels": "قانال",
"CameraImageUploadedFrom": "{0} ئورۇندىن يېڭى سۈرەت چىقىرىلدى",
"Books": "كىتاب",
- "AuthenticationSucceededWithUserName": "تىزىملىتىش مۇۋەپپەقىيەتلىك بول",
+ "AuthenticationSucceededWithUserName": "{0} تەستىقلاش مۇۋاپىقىيەتلىك بولدى",
"Artists": "سەنئەتكار",
- "Albums": "پىلاستىنكا"
+ "Albums": "پىلاستىنكا",
+ "DeviceOnlineWithName": "{0} ئۇلاندى",
+ "DeviceOfflineWithName": "{0} ئۈزۈلدى",
+ "Collections": "توپلام",
+ "Application": "ئەپ",
+ "AppDeviceValues": "ئەپ: {0}، ئۈسكۈنە: {1}",
+ "HeaderLiveTV": "تور تېلېۋىزىيەسى",
+ "Default": "سۈكۈتتىكى",
+ "Folders": "ھۆججەت خالتىسى",
+ "Favorites": "ساقلىغۇچ",
+ "LabelRunningTimeValue": "ئىجرا بولغان ۋاقتى:{0}",
+ "HeaderRecordingGroups": "خاتىرلەش گۇرۇپىسى",
+ "Forced": "ئەڭ",
+ "TaskKeyframeExtractor": "ھالقىلىق رامكا ئاجراتقۇچ",
+ "TaskKeyframeExtractorDescription": "سىن ھۆججەتلىرىدىن رامكا ئاجرىتىپ، تېخىمۇ ئېنىق بولغان HLS قويۇلۇش تىزىملىكىنى قۇرۇلىدۇ. بۇ ۋەزىپە ئۇزۇن داۋام قىلىشى مۇمكىن.",
+ "TaskOptimizeDatabase": "سانداننى ئەلالاشتۇرۇش",
+ "TaskDownloadMissingSubtitlesDescription": "ئامىللار تەڭشىكىگە ئاساسەن توردىن كەم بولغان فىلىم خېتىنى ئىزدەش.",
+ "TaskDownloadMissingSubtitles": "كەم بولغان فىلىم خەتلىرىنى چۈشۈرۈش",
+ "TasksChannelsCategory": "ئىنتېرنېت قاناللىرى",
+ "TaskRefreshChannelsDescription": "ئىنتېرنېت قانىلى ئۇچۇرىنى يېڭىلاش.",
+ "TaskRefreshChannels": "قانالنى يېڭىلاش",
+ "TaskCleanTranscodeDescription": "بىر كۈندىن ئاشقان ئالماشتۇرۇش ھۆججەتلىرىنى ئۆچۈرۈش.",
+ "TaskCleanTranscode": "ئايلاندۇرۇش ھۆججەت قىسقۇچىنى تازىلاش",
+ "TaskUpdatePluginsDescription": "ئاپتوماتىك يېڭىلاشقا بېكىتىلگەن قىستۇرمىلارنىڭ يېڭىلانمىسىنى چۈشۈرۈش ۋە قاچىلاش.",
+ "TaskUpdatePlugins": "قىستۇرمىلارنى يېڭىلاش",
+ "TaskRefreshPeopleDescription": "مېدىيا ئامبىرىدىكى ئارتىس ۋە رېژىسسورلارنىڭ ئۇچۇرىنى يېڭىلاش.",
+ "TaskRefreshPeople": "ئابونتلارنى يېڭىلاش",
+ "TaskCleanLogsDescription": "{0} كۈندىن ئاشقان Log ھۆججىتىنى ئۆچۈرۈش.",
+ "TaskCleanLogs": "Log ھۆججەت قسقۇچىنى تازىلاش",
+ "TaskRefreshLibraryDescription": "مېدىيا ئامبىرىغا قوشۇلغان يېڭى ھۆججەتلەرنى ئىزدەش ۋە مېدىيا ئۇچۇرلىرىنى يېڭىلاش.",
+ "TaskRefreshLibrary": "مېدىيا ئامبىرىنى سىكاننېرلاش",
+ "TaskRefreshChapterImagesDescription": "ۋېدىئو بۆلەكلىرى ئۈچۈن كىچىك سۈرەت ياساش.",
+ "TaskRefreshChapterImages": "بۆلەكلەر رەسىملىرىنى چىقىرىۋېلىش",
+ "TaskCleanCacheDescription": "سىستېما ئىھتىياجى يوق بولغان ۋاقىتلىق ھۆججەتلەرنى ئۆچۈرۈش.",
+ "TaskCleanCache": "ۋاقىتلىق ھۆججەت قىسقۇچنى تازىلاش",
+ "TaskCleanActivityLogDescription": "ۋاقىت تەڭشىكىدىن بۇرۇنقى پائالىيەت خاتىرىسى خاتىرىسىنى ئۆچۈرۈش.",
+ "TaskCleanActivityLog": "پروگرامما خاتىرىسىنى تازىلاش",
+ "TasksApplicationCategory": "پروگرامما",
+ "TasksLibraryCategory": "مېدىيا ئامبىرى",
+ "TasksMaintenanceCategory": "ئاسراش",
+ "VersionNumber": "نەشرى {0}",
+ "ValueSpecialEpisodeName": "خاسلىق - {0}",
+ "ValueHasBeenAddedToLibrary": "{0} مېدىيا ئامبىرىڭىزغا قوشۇلدى",
+ "UserStoppedPlayingItemWithValues": "{0}،{1} نى {2} دە قويۇنشتىن توختىدى",
+ "UserStartedPlayingItemWithValues": "{0}،{1} نى {2} دە قويۇۋاتىدۇ",
+ "UserPolicyUpdatedWithName": "ئابونتلار سىياسىتى {0} غا يېڭىلاندى",
+ "UserPasswordChangedWithName": "ئابونت{0} ئۈچۈن پارول ئۆزگەرتىلدى",
+ "UserOfflineFromDevice": "{0} بىلەن {1} نىڭ ئالاقىسى ئۈزۈلدى",
+ "UserLockedOutWithName": "ئابونت {0} قۇلۇپلاندى",
+ "UserDownloadingItemWithValues": "{0} چۈشۈرۈۋاتىدۇ {1}",
+ "UserDeletedWithName": "{0} ئابونت ئۆچۈرۈلدى",
+ "UserCreatedWithName": "{0} ئابونت يېڭىدىن قوشۇلدى",
+ "User": "ئابونت",
+ "Undefined": "بېكىتىلمىگەن",
+ "TvShows": "تىياتىرلار",
+ "System": "سىستېما",
+ "Sync": "ماس قەدەمدەش",
+ "SubtitleDownloadFailureFromForItem": "{0} دىن {0} نىڭ فىلىم خېتىنى چۈشۈرگىلى بولمىدى",
+ "StartupEmbyServerIsLoading": "Jellyfin مۇلازىمىتېرى يۈكلىنىۋاتىدۇ. سەل تۇرۇپ قايتا سىناڭ.",
+ "Songs": "ناخشىلار",
+ "Shows": "پروگراممىلار",
+ "ServerNameNeedsToBeRestarted": "{0} قايتا قوزغىتىلىشى كېرەك",
+ "ScheduledTaskStartedWithName": "{0} باشلاندى",
+ "ScheduledTaskFailedWithName": "{0} مەغلۇپ بولدى",
+ "ProviderValue": "تەمىنلىگۈچى: {0}",
+ "PluginUpdatedWithName": "{0} يېڭىلاندى",
+ "PluginUninstalledWithName": "{0} ئۆچۈرۈلدى",
+ "PluginInstalledWithName": "{0} قاچىلاندى",
+ "Plugin": "قىستۇرما",
+ "Playlists": "قويۇش تىزىملىكى",
+ "Photos": "رەسىملەر",
+ "NotificationOptionVideoPlaybackStopped": "سىن قويۇلۇش توختىدى",
+ "NotificationOptionVideoPlayback": "سىن قويۇلدى",
+ "NotificationOptionUserLockedOut": "ئابونت قۇلۇپلاندى",
+ "NotificationOptionTaskFailed": "بەلگىلەنگەن ۋەزىپە مەغلۇپ بولدى",
+ "NotificationOptionServerRestartRequired": "مۇلازىمىتېر قايتا قوزغىتىلىشى كېرەك",
+ "NotificationOptionPluginUpdateInstalled": "قىستۇرما يېڭىلانمىسى قاچىلاندى",
+ "NotificationOptionPluginInstalled": "قىستۇرما قاچىلاندى",
+ "NotificationOptionPluginUninstalled": "قىستۇرما ئۆچۈرۈلدى",
+ "NotificationOptionPluginError": "قىستۇرما خاتالىقى",
+ "NotificationOptionNewLibraryContent": "يېڭى مەزمۇن قوشۇلدى",
+ "NotificationOptionInstallationFailed": "قاچىلاش مەغلۇب بولدى",
+ "NotificationOptionCameraImageUploaded": "كامىكامېرا سۈرىتى يوللاندى",
+ "NotificationOptionAudioPlayback": "ئاۋاز قويۇش باشلاندى",
+ "NotificationOptionAudioPlaybackStopped": "ئاۋاز قويۇش توختىدى",
+ "NotificationOptionApplicationUpdateInstalled": "ئەپ يېڭىلانمىسى ئورنىتىلدى",
+ "NotificationOptionApplicationUpdateAvailable": "ئەپنىڭ نەشرىنى يېڭىلىغىلى بولۇدۇ",
+ "NewVersionIsAvailable": "Jellyfin Server نىڭ يېڭى نۇسخىسىنى چۈشۈرگىلى بولىدۇ.",
+ "NameSeasonUnknown": "نامەمۇم بۆلۈم",
+ "NameSeasonNumber": "{0}-بۆلۈم",
+ "NameInstallFailed": "{0} قاچىلاش مەغلۇپ بولدى",
+ "MusicVideos": "سىنلىق مۇزىكا",
+ "Music": "مۇزىكا",
+ "Movies": "فىلىملەر",
+ "MixedContent": "ئارىلاشما مەزمۇن",
+ "MessageNamedServerConfigurationUpdatedWithValue": "مۇلازىمىتېر تەڭشىكىنىڭ {0} قىسمى يېڭىلىنىپ بولدى",
+ "MessageServerConfigurationUpdated": "مۇلازىمىتېر يېڭىلىنىپ بولدى",
+ "MessageApplicationUpdated": "Jellyfin مۇلازىمىتېرى يېڭىلاندى",
+ "MessageApplicationUpdatedTo": "Jellyfin مۇلازىمىتېر نەشرى {0} گە يېڭىلاندى",
+ "Latest": "ئەڭ يېڭى",
+ "LabelIpAddressValue": "{0}: IP ئادرىسى",
+ "ItemRemovedWithName": "{0} ئامباردىن چىقىرىلدى",
+ "ItemAddedWithName": "{0} ئامبارغا قوشۇلدى",
+ "Inherit": "داۋاملاشتۇرۇش",
+ "HomeVideos": "ئائىلە سىنلىرى",
+ "HeaderNextUp": "كېيىنكىسى",
+ "HeaderFavoriteSongs": "ئەڭ ياقتۇرىدىغان ناخشىلار",
+ "HeaderFavoriteShows": "ئەڭ ياقتۇرىدىغان پروگراممىلار",
+ "HeaderFavoriteEpisodes": "ئەڭ ياقتۇرىدىغان تىياتېرلار",
+ "HeaderFavoriteArtists": "ئەڭ ياقتۇرىدىغان سەنئەتكارلار",
+ "HeaderFavoriteAlbums": "ياقتۇرىدىغان پىلاستىنكىلار",
+ "HeaderContinueWatching": "داۋاملىق كۆرۈش",
+ "HeaderAlbumArtists": "پىلاستىنكا سەنئەتكارلىرى",
+ "Genres": "ئۇسلۇبلار",
+ "FailedLoginAttemptWithUserName": "{0} كىرىش ئوڭۇشلۇق بولمىدى",
+ "External": "سىرتقى"
}
diff --git a/Emby.Server.Implementations/Localization/Core/uk.json b/Emby.Server.Implementations/Localization/Core/uk.json
index b1ed78b40..3e0fd11c8 100644
--- a/Emby.Server.Implementations/Localization/Core/uk.json
+++ b/Emby.Server.Implementations/Localization/Core/uk.json
@@ -10,19 +10,19 @@
"ItemAddedWithName": "{0} додано до медіатеки",
"HeaderNextUp": "Наступний",
"HeaderLiveTV": "Ефірне ТБ",
- "HeaderFavoriteSongs": "Улюблені пісні",
- "HeaderFavoriteShows": "Улюблені шоу",
- "HeaderFavoriteEpisodes": "Улюблені серії",
- "HeaderFavoriteArtists": "Улюблені виконавці",
- "HeaderFavoriteAlbums": "Улюблені альбоми",
+ "HeaderFavoriteSongs": "Обрані пісні",
+ "HeaderFavoriteShows": "Обрані шоу",
+ "HeaderFavoriteEpisodes": "Обрані епізоди",
+ "HeaderFavoriteArtists": "Обрані виконавці",
+ "HeaderFavoriteAlbums": "Обрані альбоми",
"HeaderContinueWatching": "Продовжити перегляд",
"HeaderAlbumArtists": "Виконавці альбому",
"Genres": "Жанри",
"Folders": "Каталоги",
- "Favorites": "Улюблені",
+ "Favorites": "Обрані",
"DeviceOnlineWithName": "Пристрій {0} підключився",
"DeviceOfflineWithName": "Пристрій {0} відключився",
- "Collections": "Колекції",
+ "Collections": "Добірки",
"ChapterNameValue": "Розділ {0}",
"Channels": "Канали",
"CameraImageUploadedFrom": "Нова фотографія завантажена з {0}",
@@ -119,7 +119,7 @@
"Undefined": "Не визначено",
"Default": "За замовчуванням",
"TaskOptimizeDatabase": "Оптимізувати базу даних",
- "TaskOptimizeDatabaseDescription": "Стиснення бази даних та збільшення вільного простору. Виконання цього завдання після сканування бібліотеки або внесення інших змін, які передбачають модифікацію бази даних, може покращити продуктивність.",
+ "TaskOptimizeDatabaseDescription": "Стискає базу даних та збільшує вільний простір. Виконання цього завдання після сканування медіатеки або внесення інших змін, які передбачають модифікацію бази даних може покращити продуктивність.",
"TaskKeyframeExtractorDescription": "Витягує ключові кадри з відеофайлів для створення більш точних списків відтворення HLS. Це завдання може виконуватися протягом тривалого часу.",
"TaskKeyframeExtractor": "Екстрактор ключових кадрів",
"External": "Зовнішній"
diff --git a/Emby.Server.Implementations/Localization/Core/uz.json b/Emby.Server.Implementations/Localization/Core/uz.json
new file mode 100644
index 000000000..43935f224
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/uz.json
@@ -0,0 +1,12 @@
+{
+ "HeaderContinueWatching": "Ko‘rishda davom etish",
+ "HeaderAlbumArtists": "Albom ijrochilari",
+ "Genres": "Janrlar",
+ "Folders": "Jildlar",
+ "Favorites": "Sevimlilar",
+ "Collections": "To'plamlar",
+ "Channels": "Kanallar",
+ "Books": "Kitoblar",
+ "Artists": "Ijrochilar",
+ "Albums": "Albomlar"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json
index 9f71e1690..b9e2f1e6c 100644
--- a/Emby.Server.Implementations/Localization/Core/vi.json
+++ b/Emby.Server.Implementations/Localization/Core/vi.json
@@ -91,7 +91,7 @@
"MessageApplicationUpdated": "Jellyfin Server đã được cập nhật",
"Latest": "Gần Nhất",
"LabelRunningTimeValue": "Thời Gian Chạy: {0}",
- "LabelIpAddressValue": "Địa Chỉ IP: {0}",
+ "LabelIpAddressValue": "Địa chỉ IP: {0}",
"ItemRemovedWithName": "{0} đã xóa khỏi thư viện",
"ItemAddedWithName": "{0} được thêm vào thư viện",
"Inherit": "Thừa hưởng",
diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json
index ce616cbbb..a121fc376 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-CN.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json
@@ -1,6 +1,6 @@
{
"Albums": "专辑",
- "AppDeviceValues": "应用: {0}, 设备: {1}",
+ "AppDeviceValues": "应用:{0},设备:{1}",
"Application": "应用程序",
"Artists": "艺术家",
"AuthenticationSucceededWithUserName": "{0} 认证成功",
diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json
index ac74da67d..6c8bf7627 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-HK.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json
@@ -120,5 +120,8 @@
"Default": "預設",
"TaskOptimizeDatabaseDescription": "壓縮數據庫並截斷可用空間。在掃描媒體庫或執行其他數據庫的修改後運行此任務可能會提高性能。",
"TaskOptimizeDatabase": "最佳化數據庫",
- "TaskCleanActivityLogDescription": "刪除早於設定時間的日誌記錄。"
+ "TaskCleanActivityLogDescription": "刪除早於設定時間的日誌記錄。",
+ "TaskKeyframeExtractorDescription": "提取關鍵格以創建更準確的HLS播放列表。次指示可能用時很長。",
+ "TaskKeyframeExtractor": "關鍵幀提取器",
+ "External": "外部"
}
diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json
index 601e071b6..102a266f8 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-TW.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json
@@ -41,26 +41,26 @@
"NameInstallFailed": "{0} 安裝失敗",
"NameSeasonNumber": "第 {0} 季",
"NameSeasonUnknown": "未知季數",
- "NewVersionIsAvailable": "新版本的 Jellyfin Server 軟體已經可供下載。",
+ "NewVersionIsAvailable": "新版本的 Jellyfin Server 已經可供下載。",
"NotificationOptionApplicationUpdateAvailable": "有可用的應用程式更新",
- "NotificationOptionApplicationUpdateInstalled": "軟體更新已安裝",
- "NotificationOptionAudioPlayback": "音樂開始播放",
- "NotificationOptionAudioPlaybackStopped": "音樂停止播放",
- "NotificationOptionCameraImageUploaded": "相機相片已上傳",
+ "NotificationOptionApplicationUpdateInstalled": "應用程式更新已安裝",
+ "NotificationOptionAudioPlayback": "音訊播放已開始",
+ "NotificationOptionAudioPlaybackStopped": "音訊播放已停止",
+ "NotificationOptionCameraImageUploaded": "相片已上傳",
"NotificationOptionInstallationFailed": "安裝失敗",
"NotificationOptionNewLibraryContent": "已新增新內容",
- "NotificationOptionPluginError": "外掛安裝失敗",
- "NotificationOptionPluginInstalled": "外掛已安裝",
- "NotificationOptionPluginUninstalled": "外掛已移除",
- "NotificationOptionPluginUpdateInstalled": "外掛已更新",
+ "NotificationOptionPluginError": "附加元件安裝失敗",
+ "NotificationOptionPluginInstalled": "附加元件已安裝",
+ "NotificationOptionPluginUninstalled": "附加元件已移除",
+ "NotificationOptionPluginUpdateInstalled": "附加元件已更新",
"NotificationOptionServerRestartRequired": "伺服器需要重新啟動",
"NotificationOptionTaskFailed": "排程任務失敗",
"NotificationOptionUserLockedOut": "使用者已鎖定",
- "NotificationOptionVideoPlayback": "影片開始播放",
- "NotificationOptionVideoPlaybackStopped": "影片停止播放",
+ "NotificationOptionVideoPlayback": "影片播放已開始",
+ "NotificationOptionVideoPlaybackStopped": "影片播放已停止",
"Photos": "相片",
"Playlists": "播放清單",
- "Plugin": "外掛",
+ "Plugin": "附加元件",
"PluginInstalledWithName": "{0} 已安裝",
"PluginUninstalledWithName": "{0} 已移除",
"PluginUpdatedWithName": "{0} 已更新",
@@ -70,7 +70,7 @@
"ServerNameNeedsToBeRestarted": "伺服器 {0} 需要重新啟動",
"Shows": "節目",
"Songs": "歌曲",
- "StartupEmbyServerIsLoading": "Jellyfin Server正在啟動,請稍後再試一次。",
+ "StartupEmbyServerIsLoading": "Jellyfin Server 載入中,請稍後再試。",
"Sync": "同步",
"System": "系統",
"TvShows": "電視節目",
@@ -82,23 +82,23 @@
"UserOfflineFromDevice": "使用者 {0} 已從 {1} 斷線",
"UserOnlineFromDevice": "使用者 {0} 已從 {1} 連線",
"UserPasswordChangedWithName": "使用者 {0} 的密碼已變更",
- "UserPolicyUpdatedWithName": "使用者條約已更新為 {0}",
- "UserStartedPlayingItemWithValues": "{0}正在使用 {2} 播放 {1}",
- "UserStoppedPlayingItemWithValues": "{0} 已停止在 {2} 播放 {1}",
+ "UserPolicyUpdatedWithName": "使用者協議已更新為 {0}",
+ "UserStartedPlayingItemWithValues": "{0}正在 {2} 上播放 {1}",
+ "UserStoppedPlayingItemWithValues": "{0} 已在 {2} 上停止播放 {1}",
"ValueHasBeenAddedToLibrary": "{0} 已新增至您的媒體庫",
- "ValueSpecialEpisodeName": "特典 - {0}",
+ "ValueSpecialEpisodeName": "特輯 - {0}",
"VersionNumber": "版本 {0}",
"HeaderRecordingGroups": "錄製組",
"Inherit": "繼承",
"SubtitleDownloadFailureFromForItem": "無法為 {1} 從 {0} 下載字幕",
- "TaskDownloadMissingSubtitlesDescription": "在網路上透過中繼資料搜尋遺失的字幕。",
+ "TaskDownloadMissingSubtitlesDescription": "透過中繼資料從網路上搜尋遺失的字幕。",
"TaskDownloadMissingSubtitles": "下載遺失的字幕",
"TaskRefreshChannels": "重新整理頻道",
- "TaskUpdatePlugins": "更新外掛",
- "TaskRefreshPeople": "刷新用戶",
- "TaskCleanLogsDescription": "刪除超過 {0} 天的舊紀錄檔。",
- "TaskCleanLogs": "清空紀錄資料夾",
- "TaskRefreshLibraryDescription": "重新掃描媒體庫的新檔案並更新描述資料。",
+ "TaskUpdatePlugins": "更新附加元件",
+ "TaskRefreshPeople": "更新人物",
+ "TaskCleanLogsDescription": "刪除超過 {0} 天的日誌文件。",
+ "TaskCleanLogs": "清空日誌資料夾",
+ "TaskRefreshLibraryDescription": "重新掃描媒體庫的新檔案並更新中繼資料。",
"TaskRefreshLibrary": "重新掃描媒體庫",
"TaskRefreshChapterImages": "擷取章節圖片",
"TaskCleanCacheDescription": "刪除系統已不需要的快取。",
@@ -107,7 +107,7 @@
"TaskRefreshChannelsDescription": "重新整理網路頻道資料。",
"TaskCleanTranscodeDescription": "刪除超過一天的轉碼檔案。",
"TaskCleanTranscode": "清除轉碼資料夾",
- "TaskUpdatePluginsDescription": "為設置自動更新的外掛下載並安裝更新。",
+ "TaskUpdatePluginsDescription": "為已設置為自動更新的附加元件下載並安裝更新。",
"TaskRefreshPeopleDescription": "更新媒體庫中演員和導演的中繼資料。",
"TaskRefreshChapterImagesDescription": "為有章節的影片建立縮圖。",
"TasksChannelsCategory": "網路頻道",
@@ -115,9 +115,9 @@
"TasksMaintenanceCategory": "維護",
"TaskCleanActivityLogDescription": "刪除超過所設時間的活動紀錄。",
"TaskCleanActivityLog": "清除活動紀錄",
- "Undefined": "未定義的",
+ "Undefined": "未定義",
"Forced": "強制",
- "Default": "原本",
+ "Default": "預設",
"TaskOptimizeDatabaseDescription": "縮小資料庫並釋放可用空間。在掃描資料庫或進行資料庫相關的更動後使用此功能會增加效能。",
"TaskOptimizeDatabase": "最佳化資料庫",
"TaskKeyframeExtractorDescription": "將關鍵幀從影片檔案提取出來並建立更精準的HLS播放清單。這可能需要很長時間。",
diff --git a/Emby.Server.Implementations/Localization/Ratings/fi.csv b/Emby.Server.Implementations/Localization/Ratings/fi.csv
new file mode 100644
index 000000000..782785890
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Ratings/fi.csv
@@ -0,0 +1,10 @@
+FI-S,1
+FI-T,1
+FI-7,4
+FI-12,5
+FI-16,8
+FI-18,9
+FI-K7,4
+FI-K12,5
+FI-K16,8
+FI-K18,9
diff --git a/Emby.Server.Implementations/Localization/Ratings/no.csv b/Emby.Server.Implementations/Localization/Ratings/no.csv
new file mode 100644
index 000000000..127407be8
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Ratings/no.csv
@@ -0,0 +1,6 @@
+NO-A,1
+NO-6,3
+NO-9,4
+NO-12,5
+NO-15,8
+NO-18,9
diff --git a/Emby.Server.Implementations/Localization/Ratings/se.csv b/Emby.Server.Implementations/Localization/Ratings/se.csv
new file mode 100644
index 000000000..1443c07df
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Ratings/se.csv
@@ -0,0 +1,5 @@
+SE-Btl,1
+SE-Barntillåten,1
+SE-7,3
+SE-11,5
+SE-15,8
diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs
index 21795c8f8..303875df5 100644
--- a/Emby.Server.Implementations/Net/SocketFactory.cs
+++ b/Emby.Server.Implementations/Net/SocketFactory.cs
@@ -63,10 +63,7 @@ namespace Emby.Server.Implementations.Net
/// <inheritdoc />
public ISocket CreateUdpMulticastSocket(IPAddress ipAddress, int multicastTimeToLive, int localPort)
{
- if (ipAddress == null)
- {
- throw new ArgumentNullException(nameof(ipAddress));
- }
+ ArgumentNullException.ThrowIfNull(ipAddress);
if (multicastTimeToLive <= 0)
{
diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs
index bbbca4fc0..c3994fc04 100644
--- a/Emby.Server.Implementations/Net/UdpSocket.cs
+++ b/Emby.Server.Implementations/Net/UdpSocket.cs
@@ -35,10 +35,7 @@ namespace Emby.Server.Implementations.Net
public UdpSocket(Socket socket, int localPort, IPAddress ip)
{
- if (socket == null)
- {
- throw new ArgumentNullException(nameof(socket));
- }
+ ArgumentNullException.ThrowIfNull(socket);
_socket = socket;
_localPort = localPort;
@@ -51,10 +48,7 @@ namespace Emby.Server.Implementations.Net
public UdpSocket(Socket socket, IPEndPoint endPoint)
{
- if (socket == null)
- {
- throw new ArgumentNullException(nameof(socket));
- }
+ ArgumentNullException.ThrowIfNull(socket);
_socket = socket;
_socket.Connect(endPoint);
@@ -96,7 +90,7 @@ namespace Emby.Server.Implementations.Net
}
else
{
- tcs.TrySetException(new Exception("SocketError: " + e.SocketError));
+ tcs.TrySetException(new SocketException((int)e.SocketError));
}
}
}
@@ -114,7 +108,7 @@ namespace Emby.Server.Implementations.Net
}
else
{
- tcs.TrySetException(new Exception("SocketError: " + e.SocketError));
+ tcs.TrySetException(new SocketException((int)e.SocketError));
}
}
}
diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs
index 45ef36441..ec4e0dbeb 100644
--- a/Emby.Server.Implementations/Plugins/PluginManager.cs
+++ b/Emby.Server.Implementations/Plugins/PluginManager.cs
@@ -234,10 +234,7 @@ namespace Emby.Server.Implementations.Plugins
/// <returns>Outcome of the operation.</returns>
public bool RemovePlugin(LocalPlugin plugin)
{
- if (plugin == null)
- {
- throw new ArgumentNullException(nameof(plugin));
- }
+ ArgumentNullException.ThrowIfNull(plugin);
if (DeletePlugin(plugin))
{
@@ -288,10 +285,7 @@ namespace Emby.Server.Implementations.Plugins
/// <param name="plugin">The <see cref="LocalPlugin"/> of the plug to disable.</param>
public void EnablePlugin(LocalPlugin plugin)
{
- if (plugin == null)
- {
- throw new ArgumentNullException(nameof(plugin));
- }
+ ArgumentNullException.ThrowIfNull(plugin);
if (ChangePluginState(plugin, PluginStatus.Active))
{
@@ -306,10 +300,7 @@ namespace Emby.Server.Implementations.Plugins
/// <param name="plugin">The <see cref="LocalPlugin"/> of the plug to disable.</param>
public void DisablePlugin(LocalPlugin plugin)
{
- if (plugin == null)
- {
- throw new ArgumentNullException(nameof(plugin));
- }
+ ArgumentNullException.ThrowIfNull(plugin);
// Update the manifest on disk
if (ChangePluginState(plugin, PluginStatus.Disabled))
@@ -326,10 +317,7 @@ namespace Emby.Server.Implementations.Plugins
public void FailPlugin(Assembly assembly)
{
// Only save if disabled.
- if (assembly == null)
- {
- throw new ArgumentNullException(nameof(assembly));
- }
+ ArgumentNullException.ThrowIfNull(assembly);
var plugin = _plugins.FirstOrDefault(p => p.DllFiles.Contains(assembly.Location));
if (plugin == null)
diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
index 2c4d6884d..b370e06ef 100644
--- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
@@ -92,25 +92,13 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// </exception>
public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, ILogger logger)
{
- if (scheduledTask == null)
- {
- throw new ArgumentNullException(nameof(scheduledTask));
- }
+ ArgumentNullException.ThrowIfNull(scheduledTask);
- if (applicationPaths == null)
- {
- throw new ArgumentNullException(nameof(applicationPaths));
- }
+ ArgumentNullException.ThrowIfNull(applicationPaths);
- if (taskManager == null)
- {
- throw new ArgumentNullException(nameof(taskManager));
- }
+ ArgumentNullException.ThrowIfNull(taskManager);
- if (logger == null)
- {
- throw new ArgumentNullException(nameof(logger));
- }
+ ArgumentNullException.ThrowIfNull(logger);
ScheduledTask = scheduledTask;
_applicationPaths = applicationPaths;
@@ -249,10 +237,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
get => _triggers;
set
{
- if (value == null)
- {
- throw new ArgumentNullException(nameof(value));
- }
+ ArgumentNullException.ThrowIfNull(value);
// Cleanup current triggers
if (_triggers != null)
@@ -281,10 +266,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
set
{
- if (value == null)
- {
- throw new ArgumentNullException(nameof(value));
- }
+ ArgumentNullException.ThrowIfNull(value);
// This null check is not great, but is needed to handle bad user input, or user mucking with the config file incorrectly
var triggerList = value.Where(i => i != null).ToArray();
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index 277fdf87d..0d1029882 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -329,13 +329,17 @@ namespace Emby.Server.Implementations.Session
}
/// <inheritdoc />
- public void CloseIfNeeded(SessionInfo session)
+ public async Task CloseIfNeededAsync(SessionInfo session)
{
if (!session.SessionControllers.Any(i => i.IsSessionActive))
{
var key = GetSessionKey(session.Client, session.DeviceId);
_activeConnections.TryRemove(key, out _);
+ if (!string.IsNullOrEmpty(session.PlayState?.LiveStreamId))
+ {
+ await _mediaSourceManager.CloseLiveStream(session.PlayState.LiveStreamId).ConfigureAwait(false);
+ }
OnSessionEnded(session);
}
@@ -413,6 +417,7 @@ namespace Emby.Server.Implementations.Session
session.PlayState.IsPaused = info.IsPaused;
session.PlayState.PositionTicks = info.PositionTicks;
session.PlayState.MediaSourceId = info.MediaSourceId;
+ session.PlayState.LiveStreamId = info.LiveStreamId;
session.PlayState.CanSeek = info.CanSeek;
session.PlayState.IsMuted = info.IsMuted;
session.PlayState.VolumeLevel = info.VolumeLevel;
@@ -660,10 +665,7 @@ namespace Emby.Server.Implementations.Session
{
CheckDisposed();
- if (info == null)
- {
- throw new ArgumentNullException(nameof(info));
- }
+ ArgumentNullException.ThrowIfNull(info);
var session = GetSession(info.SessionId);
@@ -699,7 +701,9 @@ namespace Emby.Server.Implementations.Session
DeviceName = session.DeviceName,
ClientName = session.Client,
DeviceId = session.DeviceId,
- Session = session
+ Session = session,
+ PlaybackPositionTicks = info.PositionTicks,
+ PlaySessionId = info.PlaySessionId
};
await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false);
@@ -755,10 +759,7 @@ namespace Emby.Server.Implementations.Session
{
CheckDisposed();
- if (info == null)
- {
- throw new ArgumentNullException(nameof(info));
- }
+ ArgumentNullException.ThrowIfNull(info);
var session = GetSession(info.SessionId);
@@ -768,6 +769,11 @@ namespace Emby.Server.Implementations.Session
await UpdateNowPlayingItem(session, info, libraryItem, !isAutomated).ConfigureAwait(false);
+ if (!string.IsNullOrEmpty(session.DeviceId) && info.PlayMethod != PlayMethod.Transcode)
+ {
+ ClearTranscodingInfo(session.DeviceId);
+ }
+
var users = GetUsers(session);
// only update saved user data on actual check-ins, not automated ones
@@ -885,10 +891,7 @@ namespace Emby.Server.Implementations.Session
{
CheckDisposed();
- if (info == null)
- {
- throw new ArgumentNullException(nameof(info));
- }
+ ArgumentNullException.ThrowIfNull(info);
if (info.PositionTicks.HasValue && info.PositionTicks.Value < 0)
{
@@ -985,7 +988,8 @@ namespace Emby.Server.Implementations.Session
DeviceName = session.DeviceName,
ClientName = session.Client,
DeviceId = session.DeviceId,
- Session = session
+ Session = session,
+ PlaySessionId = info.PlaySessionId
};
await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false);
@@ -1229,7 +1233,7 @@ namespace Emby.Server.Implementations.Session
if (item == null)
{
- _logger.LogError("A non-existant item Id {0} was passed into TranslateItemForPlayback", id);
+ _logger.LogError("A non-existent item Id {0} was passed into TranslateItemForPlayback", id);
return Array.Empty<BaseItem>();
}
@@ -1328,15 +1332,9 @@ namespace Emby.Server.Implementations.Session
private static void AssertCanControl(SessionInfo session, SessionInfo controllingSession)
{
- if (session == null)
- {
- throw new ArgumentNullException(nameof(session));
- }
+ ArgumentNullException.ThrowIfNull(session);
- if (controllingSession == null)
- {
- throw new ArgumentNullException(nameof(controllingSession));
- }
+ ArgumentNullException.ThrowIfNull(controllingSession);
}
/// <summary>
@@ -1675,10 +1673,7 @@ namespace Emby.Server.Implementations.Session
/// </summary>
private BaseItemDto GetItemInfo(BaseItem item, MediaSourceInfo mediaSource)
{
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
+ ArgumentNullException.ThrowIfNull(item);
var dtoOptions = _itemInfoDtoOptions;
@@ -1789,10 +1784,7 @@ namespace Emby.Server.Implementations.Session
/// <inheritdoc />
public Task<SessionInfo> GetSessionByAuthenticationToken(Device info, string deviceId, string remoteEndpoint, string appVersion)
{
- if (info == null)
- {
- throw new ArgumentNullException(nameof(info));
- }
+ ArgumentNullException.ThrowIfNull(info);
var user = info.UserId.Equals(default)
? null
diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
index a085ee546..c654828b1 100644
--- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
+++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
@@ -6,7 +6,7 @@ using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Extensions;
+using Jellyfin.Api.Extensions;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Net;
@@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.Session
private const float ForceKeepAliveFactor = 0.75f;
/// <summary>
- /// Lock used for accesing the KeepAlive cancellation token.
+ /// Lock used for accessing the KeepAlive cancellation token.
/// </summary>
private readonly object _keepAliveLock = new object();
@@ -54,7 +54,6 @@ namespace Emby.Server.Implementations.Session
private readonly ISessionManager _sessionManager;
private readonly ILogger<SessionWebSocketListener> _logger;
private readonly ILoggerFactory _loggerFactory;
- private readonly IAuthorizationContext _authorizationContext;
/// <summary>
/// The KeepAlive cancellation token.
@@ -67,17 +66,14 @@ namespace Emby.Server.Implementations.Session
/// <param name="logger">The logger.</param>
/// <param name="sessionManager">The session manager.</param>
/// <param name="loggerFactory">The logger factory.</param>
- /// <param name="authorizationContext">The authorization context.</param>
public SessionWebSocketListener(
ILogger<SessionWebSocketListener> logger,
ISessionManager sessionManager,
- ILoggerFactory loggerFactory,
- IAuthorizationContext authorizationContext)
+ ILoggerFactory loggerFactory)
{
_logger = logger;
_sessionManager = sessionManager;
_loggerFactory = loggerFactory;
- _authorizationContext = authorizationContext;
}
/// <inheritdoc />
@@ -111,21 +107,18 @@ namespace Emby.Server.Implementations.Session
private async Task<SessionInfo> GetSession(HttpContext httpContext, string remoteEndpoint)
{
- var authorizationInfo = await _authorizationContext.GetAuthorizationInfo(httpContext)
- .ConfigureAwait(false);
-
- if (!authorizationInfo.IsAuthenticated)
+ if (!httpContext.User.Identity?.IsAuthenticated ?? false)
{
return null;
}
- var deviceId = authorizationInfo.DeviceId;
+ var deviceId = httpContext.User.GetDeviceId();
if (httpContext.Request.Query.TryGetValue("deviceId", out var queryDeviceId))
{
deviceId = queryDeviceId;
}
- return await _sessionManager.GetSessionByAuthenticationToken(authorizationInfo.Token, deviceId, remoteEndpoint)
+ return await _sessionManager.GetSessionByAuthenticationToken(httpContext.User.GetToken(), deviceId, remoteEndpoint)
.ConfigureAwait(false);
}
diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs
index 9fa92a53a..1f3248f07 100644
--- a/Emby.Server.Implementations/Session/WebSocketController.cs
+++ b/Emby.Server.Implementations/Session/WebSocketController.cs
@@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Session
{
- public sealed class WebSocketController : ISessionController, IDisposable
+ public sealed class WebSocketController : ISessionController, IAsyncDisposable, IDisposable
{
private readonly ILogger<WebSocketController> _logger;
private readonly ISessionManager _sessionManager;
@@ -53,13 +53,13 @@ namespace Emby.Server.Implementations.Session
connection.Closed += OnConnectionClosed;
}
- private void OnConnectionClosed(object? sender, EventArgs e)
+ private async void OnConnectionClosed(object? sender, EventArgs e)
{
var connection = sender as IWebSocketConnection ?? throw new ArgumentException($"{nameof(sender)} is not of type {nameof(IWebSocketConnection)}", nameof(sender));
_logger.LogDebug("Removing websocket from session {Session}", _session.Id);
_sockets.Remove(connection);
connection.Closed -= OnConnectionClosed;
- _sessionManager.CloseIfNeeded(_session);
+ await _sessionManager.CloseIfNeededAsync(_session).ConfigureAwait(false);
}
/// <inheritdoc />
@@ -99,6 +99,23 @@ namespace Emby.Server.Implementations.Session
foreach (var socket in _sockets)
{
socket.Closed -= OnConnectionClosed;
+ socket.Dispose();
+ }
+
+ _disposed = true;
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ foreach (var socket in _sockets)
+ {
+ socket.Closed -= OnConnectionClosed;
+ await socket.DisposeAsync().ConfigureAwait(false);
}
_disposed = true;
diff --git a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
index db8b68949..2d21bd9c2 100644
--- a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
+++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
@@ -24,15 +24,9 @@ namespace Emby.Server.Implementations.Sorting
/// <returns>System.Int32.</returns>
public int Compare(BaseItem? x, BaseItem? y)
{
- if (x == null)
- {
- throw new ArgumentNullException(nameof(x));
- }
+ ArgumentNullException.ThrowIfNull(x);
- if (y == null)
- {
- throw new ArgumentNullException(nameof(y));
- }
+ ArgumentNullException.ThrowIfNull(y);
var episode1 = x as Episode;
var episode2 = y as Episode;
diff --git a/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs b/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs
index 5f142fa4b..5cb11ab46 100644
--- a/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs
+++ b/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs
@@ -23,15 +23,9 @@ namespace Emby.Server.Implementations.Sorting
/// <returns>System.Int32.</returns>
public int Compare(BaseItem? x, BaseItem? y)
{
- if (x == null)
- {
- throw new ArgumentNullException(nameof(x));
- }
+ ArgumentNullException.ThrowIfNull(x);
- if (y == null)
- {
- throw new ArgumentNullException(nameof(y));
- }
+ ArgumentNullException.ThrowIfNull(y);
return (x.CommunityRating ?? 0).CompareTo(y.CommunityRating ?? 0);
}
diff --git a/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs
index 8b460166c..6133aaccc 100644
--- a/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs
+++ b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs
@@ -24,15 +24,9 @@ namespace Emby.Server.Implementations.Sorting
/// <returns>System.Int32.</returns>
public int Compare(BaseItem? x, BaseItem? y)
{
- if (x == null)
- {
- throw new ArgumentNullException(nameof(x));
- }
+ ArgumentNullException.ThrowIfNull(x);
- if (y == null)
- {
- throw new ArgumentNullException(nameof(y));
- }
+ ArgumentNullException.ThrowIfNull(y);
return DateTime.Compare(x.DateCreated, y.DateCreated);
}
diff --git a/Emby.Server.Implementations/Sorting/IndexNumberComparer.cs b/Emby.Server.Implementations/Sorting/IndexNumberComparer.cs
index e39280a10..1bcaccd8a 100644
--- a/Emby.Server.Implementations/Sorting/IndexNumberComparer.cs
+++ b/Emby.Server.Implementations/Sorting/IndexNumberComparer.cs
@@ -24,14 +24,13 @@ namespace Emby.Server.Implementations.Sorting
/// <returns>System.Int32.</returns>
public int Compare(BaseItem? x, BaseItem? y)
{
- if (x == null)
- {
- throw new ArgumentNullException(nameof(x));
- }
+ ArgumentNullException.ThrowIfNull(x);
+
+ ArgumentNullException.ThrowIfNull(y);
- if (y == null)
+ if (!x.IndexNumber.HasValue && !y.IndexNumber.HasValue)
{
- throw new ArgumentNullException(nameof(y));
+ return 0;
}
if (!x.IndexNumber.HasValue)
diff --git a/Emby.Server.Implementations/Sorting/NameComparer.cs b/Emby.Server.Implementations/Sorting/NameComparer.cs
index c2875eeb9..93bec4db9 100644
--- a/Emby.Server.Implementations/Sorting/NameComparer.cs
+++ b/Emby.Server.Implementations/Sorting/NameComparer.cs
@@ -24,15 +24,9 @@ namespace Emby.Server.Implementations.Sorting
/// <returns>System.Int32.</returns>
public int Compare(BaseItem? x, BaseItem? y)
{
- if (x == null)
- {
- throw new ArgumentNullException(nameof(x));
- }
+ ArgumentNullException.ThrowIfNull(x);
- if (y == null)
- {
- throw new ArgumentNullException(nameof(y));
- }
+ ArgumentNullException.ThrowIfNull(y);
return string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase);
}
diff --git a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs
index a81f78ebf..ce44f99a6 100644
--- a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs
+++ b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs
@@ -31,15 +31,9 @@ namespace Emby.Server.Implementations.Sorting
/// <returns>System.Int32.</returns>
public int Compare(BaseItem? x, BaseItem? y)
{
- if (x == null)
- {
- throw new ArgumentNullException(nameof(x));
- }
-
- if (y == null)
- {
- throw new ArgumentNullException(nameof(y));
- }
+ ArgumentNullException.ThrowIfNull(x);
+
+ ArgumentNullException.ThrowIfNull(y);
var levelX = string.IsNullOrEmpty(x.OfficialRating) ? 0 : _localization.GetRatingLevel(x.OfficialRating) ?? 0;
var levelY = string.IsNullOrEmpty(y.OfficialRating) ? 0 : _localization.GetRatingLevel(y.OfficialRating) ?? 0;
diff --git a/Emby.Server.Implementations/Sorting/ParentIndexNumberComparer.cs b/Emby.Server.Implementations/Sorting/ParentIndexNumberComparer.cs
index ffc4e0cad..c54750843 100644
--- a/Emby.Server.Implementations/Sorting/ParentIndexNumberComparer.cs
+++ b/Emby.Server.Implementations/Sorting/ParentIndexNumberComparer.cs
@@ -24,14 +24,13 @@ namespace Emby.Server.Implementations.Sorting
/// <returns>System.Int32.</returns>
public int Compare(BaseItem? x, BaseItem? y)
{
- if (x == null)
- {
- throw new ArgumentNullException(nameof(x));
- }
+ ArgumentNullException.ThrowIfNull(x);
+
+ ArgumentNullException.ThrowIfNull(y);
- if (y == null)
+ if (!x.ParentIndexNumber.HasValue && !y.ParentIndexNumber.HasValue)
{
- throw new ArgumentNullException(nameof(y));
+ return 0;
}
if (!x.ParentIndexNumber.HasValue)
diff --git a/Emby.Server.Implementations/Sorting/RuntimeComparer.cs b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs
index e32e5552e..646bafbb5 100644
--- a/Emby.Server.Implementations/Sorting/RuntimeComparer.cs
+++ b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs
@@ -26,15 +26,9 @@ namespace Emby.Server.Implementations.Sorting
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
- if (x == null)
- {
- throw new ArgumentNullException(nameof(x));
- }
+ ArgumentNullException.ThrowIfNull(x);
- if (y == null)
- {
- throw new ArgumentNullException(nameof(y));
- }
+ ArgumentNullException.ThrowIfNull(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 79be9a89a..628b9b3dd 100644
--- a/Emby.Server.Implementations/Sorting/SortNameComparer.cs
+++ b/Emby.Server.Implementations/Sorting/SortNameComparer.cs
@@ -26,15 +26,9 @@ namespace Emby.Server.Implementations.Sorting
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
- if (x == null)
- {
- throw new ArgumentNullException(nameof(x));
- }
+ ArgumentNullException.ThrowIfNull(x);
- if (y == null)
- {
- throw new ArgumentNullException(nameof(y));
- }
+ ArgumentNullException.ThrowIfNull(y);
return string.Compare(x.SortName, y.SortName, StringComparison.OrdinalIgnoreCase);
}
diff --git a/Emby.Server.Implementations/Sorting/StudioComparer.cs b/Emby.Server.Implementations/Sorting/StudioComparer.cs
index 4d89cfa8b..457c06271 100644
--- a/Emby.Server.Implementations/Sorting/StudioComparer.cs
+++ b/Emby.Server.Implementations/Sorting/StudioComparer.cs
@@ -3,7 +3,6 @@
#pragma warning disable CS1591
using System;
-using System.Linq;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
@@ -27,15 +26,9 @@ namespace Emby.Server.Implementations.Sorting
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
- if (x == null)
- {
- throw new ArgumentNullException(nameof(x));
- }
+ ArgumentNullException.ThrowIfNull(x);
- if (y == null)
- {
- throw new ArgumentNullException(nameof(y));
- }
+ ArgumentNullException.ThrowIfNull(y);
return AlphanumericComparator.CompareValues(x.Studios.FirstOrDefault(), y.Studios.FirstOrDefault());
}
diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs
index 727b9d4b5..6005896ad 100644
--- a/Emby.Server.Implementations/TV/TVSeriesManager.cs
+++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs
@@ -43,9 +43,9 @@ namespace Emby.Server.Implementations.TV
}
string presentationUniqueKey = null;
- if (!string.IsNullOrEmpty(query.SeriesId))
+ if (query.SeriesId.HasValue && !query.SeriesId.Value.Equals(default))
{
- if (_libraryManager.GetItemById(query.SeriesId) is Series series)
+ if (_libraryManager.GetItemById(query.SeriesId.Value) is Series series)
{
presentationUniqueKey = GetUniqueSeriesKey(series);
}
@@ -93,9 +93,9 @@ namespace Emby.Server.Implementations.TV
string presentationUniqueKey = null;
int? limit = null;
- if (!string.IsNullOrEmpty(request.SeriesId))
+ if (request.SeriesId.HasValue && !request.SeriesId.Value.Equals(default))
{
- if (_libraryManager.GetItemById(request.SeriesId) is Series series)
+ if (_libraryManager.GetItemById(request.SeriesId.Value) is Series series)
{
presentationUniqueKey = GetUniqueSeriesKey(series);
limit = 1;
@@ -135,25 +135,20 @@ namespace Emby.Server.Implementations.TV
return GetResult(episodes, request);
}
- public IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IReadOnlyList<string> seriesKeys, DtoOptions dtoOptions)
+ private IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IReadOnlyList<string> seriesKeys, DtoOptions dtoOptions)
{
- // Avoid implicitly captured closure
- var currentUser = user;
-
- var allNextUp = seriesKeys
- .Select(i => GetNextUp(i, currentUser, dtoOptions, false));
+ var allNextUp = seriesKeys.Select(i => GetNextUp(i, user, dtoOptions, false));
if (request.EnableRewatching)
{
allNextUp = allNextUp.Concat(
- seriesKeys.Select(i => GetNextUp(i, currentUser, dtoOptions, true))
- )
- .OrderByDescending(i => i.Item1);
+ seriesKeys.Select(i => GetNextUp(i, user, dtoOptions, true)))
+ .OrderByDescending(i => i.LastWatchedDate);
}
// If viewing all next up for all series, remove first episodes
// But if that returns empty, keep those first episodes (avoid completely empty view)
- var alwaysEnableFirstEpisode = !string.IsNullOrEmpty(request.SeriesId);
+ var alwaysEnableFirstEpisode = request.SeriesId.HasValue && !request.SeriesId.Value.Equals(default);
var anyFound = false;
return allNextUp
@@ -161,23 +156,18 @@ namespace Emby.Server.Implementations.TV
{
if (request.DisableFirstEpisode)
{
- return i.Item1 != DateTime.MinValue;
+ return i.LastWatchedDate != DateTime.MinValue;
}
- if (alwaysEnableFirstEpisode || (i.Item1 != DateTime.MinValue && i.Item1.Date >= request.NextUpDateCutoff))
+ if (alwaysEnableFirstEpisode || (i.LastWatchedDate != DateTime.MinValue && i.LastWatchedDate.Date >= request.NextUpDateCutoff))
{
anyFound = true;
return true;
}
- if (!anyFound && i.Item1 == DateTime.MinValue)
- {
- return true;
- }
-
- return false;
+ return !anyFound && i.LastWatchedDate == DateTime.MinValue;
})
- .Select(i => i.Item2())
+ .Select(i => i.GetEpisodeFunction())
.Where(i => i != null);
}
@@ -195,14 +185,14 @@ namespace Emby.Server.Implementations.TV
/// Gets the next up.
/// </summary>
/// <returns>Task{Episode}.</returns>
- private Tuple<DateTime, Func<Episode>> GetNextUp(string seriesKey, User user, DtoOptions dtoOptions, bool rewatching)
+ private (DateTime LastWatchedDate, Func<Episode> GetEpisodeFunction) GetNextUp(string seriesKey, User user, DtoOptions dtoOptions, bool rewatching)
{
var lastQuery = new InternalItemsQuery(user)
{
AncestorWithPresentationUniqueKey = null,
SeriesPresentationUniqueKey = seriesKey,
IncludeItemTypes = new[] { BaseItemKind.Episode },
- OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Descending) },
+ OrderBy = new[] { (ItemSortBy.ParentIndexNumber, SortOrder.Descending), (ItemSortBy.IndexNumber, SortOrder.Descending) },
IsPlayed = true,
Limit = 1,
ParentIndexNumberNotEquals = 0,
@@ -216,24 +206,23 @@ namespace Emby.Server.Implementations.TV
if (rewatching)
{
// find last watched by date played, not by newest episode watched
- lastQuery.OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) };
+ lastQuery.OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending), (ItemSortBy.ParentIndexNumber, SortOrder.Descending), (ItemSortBy.IndexNumber, SortOrder.Descending) };
}
var lastWatchedEpisode = _libraryManager.GetItemList(lastQuery).Cast<Episode>().FirstOrDefault();
- Func<Episode> getEpisode = () =>
+ Episode GetEpisode()
{
var nextQuery = new InternalItemsQuery(user)
{
AncestorWithPresentationUniqueKey = null,
SeriesPresentationUniqueKey = seriesKey,
IncludeItemTypes = new[] { BaseItemKind.Episode },
- OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
+ OrderBy = new[] { (ItemSortBy.ParentIndexNumber, SortOrder.Ascending), (ItemSortBy.IndexNumber, SortOrder.Ascending) },
Limit = 1,
IsPlayed = rewatching,
IsVirtualItem = false,
ParentIndexNumberNotEquals = 0,
- MinSortName = lastWatchedEpisode?.SortName,
DtoOptions = dtoOptions
};
@@ -297,7 +286,7 @@ namespace Emby.Server.Implementations.TV
}
return nextEpisode;
- };
+ }
if (lastWatchedEpisode != null)
{
@@ -305,11 +294,11 @@ namespace Emby.Server.Implementations.TV
var lastWatchedDate = userData.LastPlayedDate ?? DateTime.MinValue.AddDays(1);
- return new Tuple<DateTime, Func<Episode>>(lastWatchedDate, getEpisode);
+ return (lastWatchedDate, GetEpisode);
}
// Return the first episode
- return new Tuple<DateTime, Func<Episode>>(DateTime.MinValue, getEpisode);
+ return (DateTime.MinValue, GetEpisode);
}
private static QueryResult<BaseItem> GetResult(IEnumerable<BaseItem> items, NextUpQuery query)
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index 40c386e82..550f0e075 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -294,10 +294,7 @@ namespace Emby.Server.Implementations.Updates
/// <inheritdoc />
public async Task InstallPackage(InstallationInfo package, CancellationToken cancellationToken)
{
- if (package == null)
- {
- throw new ArgumentNullException(nameof(package));
- }
+ ArgumentNullException.ThrowIfNull(package);
var innerCancellationTokenSource = new CancellationTokenSource();
diff --git a/Jellyfin.Api/Attributes/DlnaEnabledAttribute.cs b/Jellyfin.Api/Attributes/DlnaEnabledAttribute.cs
new file mode 100644
index 000000000..d3a6ac9c8
--- /dev/null
+++ b/Jellyfin.Api/Attributes/DlnaEnabledAttribute.cs
@@ -0,0 +1,25 @@
+using Emby.Dlna;
+using MediaBrowser.Controller.Configuration;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Jellyfin.Api.Attributes;
+
+/// <inheritdoc />
+public sealed class DlnaEnabledAttribute : ActionFilterAttribute
+{
+ /// <inheritdoc />
+ public override void OnActionExecuting(ActionExecutingContext context)
+ {
+ var serverConfigurationManager = context.HttpContext.RequestServices.GetRequiredService<IServerConfigurationManager>();
+
+ var enabled = serverConfigurationManager.GetDlnaConfiguration().EnableServer;
+
+ if (!enabled)
+ {
+ context.Result = new StatusCodeResult(StatusCodes.Status503ServiceUnavailable);
+ }
+ }
+}
diff --git a/Jellyfin.Api/Attributes/HttpSubscribeAttribute.cs b/Jellyfin.Api/Attributes/HttpSubscribeAttribute.cs
index af8727552..4dcf5976a 100644
--- a/Jellyfin.Api/Attributes/HttpSubscribeAttribute.cs
+++ b/Jellyfin.Api/Attributes/HttpSubscribeAttribute.cs
@@ -25,11 +25,6 @@ namespace Jellyfin.Api.Attributes
/// <param name="template">The route template. May not be null.</param>
public HttpSubscribeAttribute(string template)
: base(_supportedMethods, template)
- {
- if (template == null)
- {
- throw new ArgumentNullException(nameof(template));
- }
- }
+ => ArgumentNullException.ThrowIfNull(template);
}
}
diff --git a/Jellyfin.Api/Attributes/HttpUnsubscribeAttribute.cs b/Jellyfin.Api/Attributes/HttpUnsubscribeAttribute.cs
index 1c0b70e71..d0238424a 100644
--- a/Jellyfin.Api/Attributes/HttpUnsubscribeAttribute.cs
+++ b/Jellyfin.Api/Attributes/HttpUnsubscribeAttribute.cs
@@ -25,11 +25,6 @@ namespace Jellyfin.Api.Attributes
/// <param name="template">The route template. May not be null.</param>
public HttpUnsubscribeAttribute(string template)
: base(_supportedMethods, template)
- {
- if (template == null)
- {
- throw new ArgumentNullException(nameof(template));
- }
- }
+ => ArgumentNullException.ThrowIfNull(template);
}
}
diff --git a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs b/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
index 13d3257df..92ee1dd59 100644
--- a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
+++ b/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
@@ -1,4 +1,5 @@
using System.Security.Claims;
+using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions;
@@ -51,21 +52,21 @@ namespace Jellyfin.Api.Auth
bool requiredDownloadPermission = false)
{
// ApiKey is currently global admin, always allow.
- var isApiKey = ClaimHelpers.GetIsApiKey(claimsPrincipal);
+ var isApiKey = claimsPrincipal.GetIsApiKey();
if (isApiKey)
{
return true;
}
// Ensure claim has userId.
- var userId = ClaimHelpers.GetUserId(claimsPrincipal);
- if (!userId.HasValue)
+ var userId = claimsPrincipal.GetUserId();
+ if (userId.Equals(default))
{
return false;
}
// Ensure userId links to a valid user.
- var user = _userManager.GetUserById(userId.Value);
+ var user = _userManager.GetUserById(userId);
if (user == null)
{
return false;
diff --git a/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs b/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs
index e6c04eb08..cdd7d8a52 100644
--- a/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs
+++ b/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
+using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Net;
@@ -44,14 +45,14 @@ namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy
return Task.CompletedTask;
}
- var userId = ClaimHelpers.GetUserId(context.User);
- var user = _userManager.GetUserById(userId!.Value);
+ var userId = context.User.GetUserId();
+ var user = _userManager.GetUserById(userId);
if (requirement.RequiredAccess == SyncPlayAccessRequirementType.HasAccess)
{
if (user.SyncPlayAccess == SyncPlayUserAccessType.CreateAndJoinGroups
|| user.SyncPlayAccess == SyncPlayUserAccessType.JoinGroups
- || _syncPlayManager.IsUserActive(userId.Value))
+ || _syncPlayManager.IsUserActive(userId))
{
context.Succeed(requirement);
}
@@ -85,7 +86,7 @@ namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy
}
else if (requirement.RequiredAccess == SyncPlayAccessRequirementType.IsInGroup)
{
- if (_syncPlayManager.IsUserActive(userId.Value))
+ if (_syncPlayManager.IsUserActive(userId))
{
context.Succeed(requirement);
}
diff --git a/Jellyfin.Api/BaseJellyfinApiController.cs b/Jellyfin.Api/BaseJellyfinApiController.cs
index 59d6b7513..0c63d24b7 100644
--- a/Jellyfin.Api/BaseJellyfinApiController.cs
+++ b/Jellyfin.Api/BaseJellyfinApiController.cs
@@ -1,4 +1,6 @@
+using System.Collections.Generic;
using System.Net.Mime;
+using Jellyfin.Api.Results;
using Jellyfin.Extensions.Json;
using Microsoft.AspNetCore.Mvc;
@@ -15,5 +17,40 @@ namespace Jellyfin.Api
JsonDefaults.PascalCaseMediaType)]
public class BaseJellyfinApiController : ControllerBase
{
+ /// <summary>
+ /// Create a new <see cref="OkResult{T}"/>.
+ /// </summary>
+ /// <param name="value">The value to return.</param>
+ /// <typeparam name="T">The type to return.</typeparam>
+ /// <returns>The <see cref="ActionResult{T}"/>.</returns>
+ protected ActionResult<IEnumerable<T>> Ok<T>(List<T> value)
+ => new OkResult<IEnumerable<T>>(value);
+
+ /// <summary>
+ /// Create a new <see cref="OkResult{T}"/>.
+ /// </summary>
+ /// <param name="value">The value to return.</param>
+ /// <typeparam name="T">The type to return.</typeparam>
+ /// <returns>The <see cref="ActionResult{T}"/>.</returns>
+ protected ActionResult<IEnumerable<T>> Ok<T>(IReadOnlyList<T> value)
+ => new OkResult<IEnumerable<T>>(value);
+
+ /// <summary>
+ /// Create a new <see cref="OkResult{T}"/>.
+ /// </summary>
+ /// <param name="value">The value to return.</param>
+ /// <typeparam name="T">The type to return.</typeparam>
+ /// <returns>The <see cref="ActionResult{T}"/>.</returns>
+ protected ActionResult<IEnumerable<T>> Ok<T>(IEnumerable<T>? value)
+ => new OkResult<IEnumerable<T>?>(value);
+
+ /// <summary>
+ /// Create a new <see cref="OkResult{T}"/>.
+ /// </summary>
+ /// <param name="value">The value to return.</param>
+ /// <typeparam name="T">The type to return.</typeparam>
+ /// <returns>The <see cref="ActionResult{T}"/>.</returns>
+ protected ActionResult<T> Ok<T>(T value)
+ => new OkResult<T>(value);
}
}
diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs
index 44796bcc4..c059cb198 100644
--- a/Jellyfin.Api/Controllers/ArtistsController.cs
+++ b/Jellyfin.Api/Controllers/ArtistsController.cs
@@ -120,7 +120,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool enableTotalRecordCount = true)
{
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
User? user = null;
@@ -323,7 +323,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool enableTotalRecordCount = true)
{
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
User? user = null;
@@ -463,7 +463,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<BaseItemDto> GetArtistByName([FromRoute, Required] string name, [FromQuery] Guid? userId)
{
- var dtoOptions = new DtoOptions().AddClientFields(Request);
+ var dtoOptions = new DtoOptions().AddClientFields(User);
var item = _libraryManager.GetArtist(name, dtoOptions);
diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs
index 54ac06276..94f7a7b82 100644
--- a/Jellyfin.Api/Controllers/AudioController.cs
+++ b/Jellyfin.Api/Controllers/AudioController.cs
@@ -207,7 +207,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
/// <param name="playSessionId">The play session id.</param>
/// <param name="segmentContainer">The segment container.</param>
- /// <param name="segmentLength">The segment lenght.</param>
+ /// <param name="segmentLength">The segment length.</param>
/// <param name="minSegments">The minimum number of segments.</param>
/// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
/// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
diff --git a/Jellyfin.Api/Controllers/ClientLogController.cs b/Jellyfin.Api/Controllers/ClientLogController.cs
index 98fd22430..ed073a687 100644
--- a/Jellyfin.Api/Controllers/ClientLogController.cs
+++ b/Jellyfin.Api/Controllers/ClientLogController.cs
@@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
+using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.ClientLogDtos;
using MediaBrowser.Controller.ClientEvent;
@@ -69,10 +70,10 @@ namespace Jellyfin.Api.Controllers
private (string ClientName, string ClientVersion) GetRequestInformation()
{
- var clientName = ClaimHelpers.GetClient(HttpContext.User) ?? "unknown-client";
- var clientVersion = ClaimHelpers.GetIsApiKey(HttpContext.User)
+ var clientName = HttpContext.User.GetClient() ?? "unknown-client";
+ var clientVersion = HttpContext.User.GetIsApiKey()
? "apikey"
- : ClaimHelpers.GetVersion(HttpContext.User) ?? "unknown-version";
+ : HttpContext.User.GetVersion() ?? "unknown-version";
return (clientName, clientVersion);
}
diff --git a/Jellyfin.Api/Controllers/CollectionController.cs b/Jellyfin.Api/Controllers/CollectionController.cs
index 8a98d856c..effc9ed7a 100644
--- a/Jellyfin.Api/Controllers/CollectionController.cs
+++ b/Jellyfin.Api/Controllers/CollectionController.cs
@@ -6,7 +6,6 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.ModelBinders;
using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Collections;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
@@ -23,22 +22,18 @@ namespace Jellyfin.Api.Controllers
{
private readonly ICollectionManager _collectionManager;
private readonly IDtoService _dtoService;
- private readonly IAuthorizationContext _authContext;
/// <summary>
/// Initializes a new instance of the <see cref="CollectionController"/> class.
/// </summary>
/// <param name="collectionManager">Instance of <see cref="ICollectionManager"/> interface.</param>
/// <param name="dtoService">Instance of <see cref="IDtoService"/> interface.</param>
- /// <param name="authContext">Instance of <see cref="IAuthorizationContext"/> interface.</param>
public CollectionController(
ICollectionManager collectionManager,
- IDtoService dtoService,
- IAuthorizationContext authContext)
+ IDtoService dtoService)
{
_collectionManager = collectionManager;
_dtoService = dtoService;
- _authContext = authContext;
}
/// <summary>
@@ -58,7 +53,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] Guid? parentId,
[FromQuery] bool isLocked = false)
{
- var userId = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).UserId;
+ var userId = User.GetUserId();
var item = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions
{
@@ -69,7 +64,7 @@ namespace Jellyfin.Api.Controllers
UserIds = new[] { userId }
}).ConfigureAwait(false);
- var dtoOptions = new DtoOptions().AddClientFields(Request);
+ var dtoOptions = new DtoOptions().AddClientFields(User);
var dto = _dtoService.GetBaseItemDto(item, dtoOptions);
diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs
index 60529e990..bbe163312 100644
--- a/Jellyfin.Api/Controllers/ConfigurationController.cs
+++ b/Jellyfin.Api/Controllers/ConfigurationController.cs
@@ -2,7 +2,6 @@ using System;
using System.ComponentModel.DataAnnotations;
using System.Net.Mime;
using System.Text.Json;
-using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Models.ConfigurationDtos;
@@ -86,21 +85,23 @@ namespace Jellyfin.Api.Controllers
/// Updates named configuration.
/// </summary>
/// <param name="key">Configuration key.</param>
+ /// <param name="configuration">Configuration.</param>
/// <response code="204">Named configuration updated.</response>
/// <returns>Update status.</returns>
[HttpPost("Configuration/{key}")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public async Task<ActionResult> UpdateNamedConfiguration([FromRoute, Required] string key)
+ public ActionResult UpdateNamedConfiguration([FromRoute, Required] string key, [FromBody, Required] JsonDocument configuration)
{
var configurationType = _configurationManager.GetConfigurationType(key);
- var configuration = await JsonSerializer.DeserializeAsync(Request.Body, configurationType, _serializerOptions).ConfigureAwait(false);
- if (configuration == null)
+ var deserializedConfiguration = configuration.Deserialize(configurationType, _serializerOptions);
+
+ if (deserializedConfiguration == null)
{
throw new ArgumentException("Body doesn't contain a valid configuration");
}
- _configurationManager.SaveConfiguration(key, configuration);
+ _configurationManager.SaveConfiguration(key, deserializedConfiguration);
return NoContent();
}
diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
index 27eb22339..64ee5680c 100644
--- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
+++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
@@ -89,12 +89,9 @@ namespace Jellyfin.Api.Controllers
// Load all custom display preferences
var customDisplayPreferences = _displayPreferencesManager.ListCustomItemDisplayPreferences(displayPreferences.UserId, itemId, displayPreferences.Client);
- if (customDisplayPreferences != null)
+ foreach (var (key, value) in customDisplayPreferences)
{
- foreach (var (key, value) in customDisplayPreferences)
- {
- dto.CustomPrefs.TryAdd(key, value);
- }
+ dto.CustomPrefs.TryAdd(key, value);
}
// This will essentially be a noop if no changes have been made, but new prefs must be saved at least.
diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs
index b1c576c33..8859d6020 100644
--- a/Jellyfin.Api/Controllers/DlnaServerController.cs
+++ b/Jellyfin.Api/Controllers/DlnaServerController.cs
@@ -20,6 +20,7 @@ namespace Jellyfin.Api.Controllers
/// Dlna Server Controller.
/// </summary>
[Route("Dlna")]
+ [DlnaEnabled]
[Authorize(Policy = Policies.AnonymousLanAccessPolicy)]
public class DlnaServerController : BaseJellyfinApiController
{
@@ -53,17 +54,12 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
[Produces(MediaTypeNames.Text.Xml)]
[ProducesFile(MediaTypeNames.Text.Xml)]
- public ActionResult GetDescriptionXml([FromRoute, Required] string serverId)
+ public ActionResult<string> GetDescriptionXml([FromRoute, Required] string serverId)
{
- if (DlnaEntryPoint.Enabled)
- {
- var url = GetAbsoluteUri();
- var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
- var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, serverId, serverAddress);
- return Ok(xml);
- }
-
- return StatusCode(StatusCodes.Status503ServiceUnavailable);
+ var url = GetAbsoluteUri();
+ var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
+ var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, serverId, serverAddress);
+ return Ok(xml);
}
/// <summary>
@@ -81,14 +77,9 @@ namespace Jellyfin.Api.Controllers
[Produces(MediaTypeNames.Text.Xml)]
[ProducesFile(MediaTypeNames.Text.Xml)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
- public ActionResult GetContentDirectory([FromRoute, Required] string serverId)
+ public ActionResult<string> GetContentDirectory([FromRoute, Required] string serverId)
{
- if (DlnaEntryPoint.Enabled)
- {
- return Ok(_contentDirectory.GetServiceXml());
- }
-
- return StatusCode(StatusCodes.Status503ServiceUnavailable);
+ return Ok(_contentDirectory.GetServiceXml());
}
/// <summary>
@@ -106,14 +97,9 @@ namespace Jellyfin.Api.Controllers
[Produces(MediaTypeNames.Text.Xml)]
[ProducesFile(MediaTypeNames.Text.Xml)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
- public ActionResult GetMediaReceiverRegistrar([FromRoute, Required] string serverId)
+ public ActionResult<string> GetMediaReceiverRegistrar([FromRoute, Required] string serverId)
{
- if (DlnaEntryPoint.Enabled)
- {
- return Ok(_mediaReceiverRegistrar.GetServiceXml());
- }
-
- return StatusCode(StatusCodes.Status503ServiceUnavailable);
+ return Ok(_mediaReceiverRegistrar.GetServiceXml());
}
/// <summary>
@@ -131,14 +117,9 @@ namespace Jellyfin.Api.Controllers
[Produces(MediaTypeNames.Text.Xml)]
[ProducesFile(MediaTypeNames.Text.Xml)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
- public ActionResult GetConnectionManager([FromRoute, Required] string serverId)
+ public ActionResult<string> GetConnectionManager([FromRoute, Required] string serverId)
{
- if (DlnaEntryPoint.Enabled)
- {
- return Ok(_connectionManager.GetServiceXml());
- }
-
- return StatusCode(StatusCodes.Status503ServiceUnavailable);
+ return Ok(_connectionManager.GetServiceXml());
}
/// <summary>
@@ -155,12 +136,7 @@ namespace Jellyfin.Api.Controllers
[ProducesFile(MediaTypeNames.Text.Xml)]
public async Task<ActionResult<ControlResponse>> ProcessContentDirectoryControlRequest([FromRoute, Required] string serverId)
{
- if (DlnaEntryPoint.Enabled)
- {
- return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false);
- }
-
- return StatusCode(StatusCodes.Status503ServiceUnavailable);
+ return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false);
}
/// <summary>
@@ -177,12 +153,7 @@ namespace Jellyfin.Api.Controllers
[ProducesFile(MediaTypeNames.Text.Xml)]
public async Task<ActionResult<ControlResponse>> ProcessConnectionManagerControlRequest([FromRoute, Required] string serverId)
{
- if (DlnaEntryPoint.Enabled)
- {
- return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false);
- }
-
- return StatusCode(StatusCodes.Status503ServiceUnavailable);
+ return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false);
}
/// <summary>
@@ -199,12 +170,7 @@ namespace Jellyfin.Api.Controllers
[ProducesFile(MediaTypeNames.Text.Xml)]
public async Task<ActionResult<ControlResponse>> ProcessMediaReceiverRegistrarControlRequest([FromRoute, Required] string serverId)
{
- if (DlnaEntryPoint.Enabled)
- {
- return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false);
- }
-
- return StatusCode(StatusCodes.Status503ServiceUnavailable);
+ return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false);
}
/// <summary>
@@ -224,12 +190,7 @@ namespace Jellyfin.Api.Controllers
[ProducesFile(MediaTypeNames.Text.Xml)]
public ActionResult<EventSubscriptionResponse> ProcessMediaReceiverRegistrarEventRequest(string serverId)
{
- if (DlnaEntryPoint.Enabled)
- {
- return ProcessEventRequest(_mediaReceiverRegistrar);
- }
-
- return StatusCode(StatusCodes.Status503ServiceUnavailable);
+ return ProcessEventRequest(_mediaReceiverRegistrar);
}
/// <summary>
@@ -249,12 +210,7 @@ namespace Jellyfin.Api.Controllers
[ProducesFile(MediaTypeNames.Text.Xml)]
public ActionResult<EventSubscriptionResponse> ProcessContentDirectoryEventRequest(string serverId)
{
- if (DlnaEntryPoint.Enabled)
- {
- return ProcessEventRequest(_contentDirectory);
- }
-
- return StatusCode(StatusCodes.Status503ServiceUnavailable);
+ return ProcessEventRequest(_contentDirectory);
}
/// <summary>
@@ -274,12 +230,7 @@ namespace Jellyfin.Api.Controllers
[ProducesFile(MediaTypeNames.Text.Xml)]
public ActionResult<EventSubscriptionResponse> ProcessConnectionManagerEventRequest(string serverId)
{
- if (DlnaEntryPoint.Enabled)
- {
- return ProcessEventRequest(_connectionManager);
- }
-
- return StatusCode(StatusCodes.Status503ServiceUnavailable);
+ return ProcessEventRequest(_connectionManager);
}
/// <summary>
@@ -299,12 +250,7 @@ namespace Jellyfin.Api.Controllers
[ProducesImageFile]
public ActionResult GetIconId([FromRoute, Required] string serverId, [FromRoute, Required] string fileName)
{
- if (DlnaEntryPoint.Enabled)
- {
- return GetIconInternal(fileName);
- }
-
- return StatusCode(StatusCodes.Status503ServiceUnavailable);
+ return GetIconInternal(fileName);
}
/// <summary>
@@ -322,12 +268,7 @@ namespace Jellyfin.Api.Controllers
[ProducesImageFile]
public ActionResult GetIcon([FromRoute, Required] string fileName)
{
- if (DlnaEntryPoint.Enabled)
- {
- return GetIconInternal(fileName);
- }
-
- return StatusCode(StatusCodes.Status503ServiceUnavailable);
+ return GetIconInternal(fileName);
}
private ActionResult GetIconInternal(string fileName)
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index f8e8d975c..0f4d3c1eb 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -20,7 +20,6 @@ using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.IO;
@@ -46,7 +45,6 @@ namespace Jellyfin.Api.Controllers
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
private readonly IDlnaManager _dlnaManager;
- private readonly IAuthorizationContext _authContext;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IMediaEncoder _mediaEncoder;
@@ -65,7 +63,6 @@ namespace Jellyfin.Api.Controllers
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
- /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
@@ -80,7 +77,6 @@ namespace Jellyfin.Api.Controllers
ILibraryManager libraryManager,
IUserManager userManager,
IDlnaManager dlnaManager,
- IAuthorizationContext authContext,
IMediaSourceManager mediaSourceManager,
IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder,
@@ -95,7 +91,6 @@ namespace Jellyfin.Api.Controllers
_libraryManager = libraryManager;
_userManager = userManager;
_dlnaManager = dlnaManager;
- _authContext = authContext;
_mediaSourceManager = mediaSourceManager;
_serverConfigurationManager = serverConfigurationManager;
_mediaEncoder = mediaEncoder;
@@ -121,7 +116,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
/// <param name="playSessionId">The play session id.</param>
/// <param name="segmentContainer">The segment container.</param>
- /// <param name="segmentLength">The segment lenght.</param>
+ /// <param name="segmentLength">The segment length.</param>
/// <param name="minSegments">The minimum number of segments.</param>
/// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
/// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
@@ -285,10 +280,9 @@ namespace Jellyfin.Api.Controllers
// Due to CTS.Token calling ThrowIfDisposed (https://github.com/dotnet/runtime/issues/29970) we have to "cache" the token
// since it gets disposed when ffmpeg exits
var cancellationToken = cancellationTokenSource.Token;
- using var state = await StreamingHelpers.GetStreamingState(
+ var state = await StreamingHelpers.GetStreamingState(
streamingRequest,
- Request,
- _authContext,
+ HttpContext,
_mediaSourceManager,
_userManager,
_libraryManager,
@@ -1393,8 +1387,7 @@ namespace Jellyfin.Api.Controllers
{
using var state = await StreamingHelpers.GetStreamingState(
streamingRequest,
- Request,
- _authContext,
+ HttpContext,
_mediaSourceManager,
_userManager,
_libraryManager,
@@ -1414,7 +1407,8 @@ namespace Jellyfin.Api.Controllers
state.RunTimeTicks ?? 0,
state.Request.SegmentContainer ?? string.Empty,
"hls1/main/",
- Request.QueryString.ToString());
+ Request.QueryString.ToString(),
+ EncodingHelper.IsCopyCodec(state.OutputVideoCodec));
var playlist = _dynamicHlsPlaylistGenerator.CreateMainPlaylist(request);
return new FileContentResult(Encoding.UTF8.GetBytes(playlist), MimeTypes.GetMimeType("playlist.m3u8"));
@@ -1431,10 +1425,9 @@ namespace Jellyfin.Api.Controllers
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
- using var state = await StreamingHelpers.GetStreamingState(
+ var state = await StreamingHelpers.GetStreamingState(
streamingRequest,
- Request,
- _authContext,
+ HttpContext,
_mediaSourceManager,
_userManager,
_libraryManager,
@@ -1711,20 +1704,30 @@ namespace Jellyfin.Api.Controllers
return audioTranscodeParams;
}
+ // flac and opus are experimental in mp4 muxer
+ var strictArgs = string.Empty;
+
+ if (string.Equals(state.ActualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(state.ActualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase))
+ {
+ strictArgs = " -strict -2";
+ }
+
if (EncodingHelper.IsCopyCodec(audioCodec))
{
var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions);
var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container);
+ var copyArgs = "-codec:a:0 copy" + bitStreamArgs + strictArgs;
if (EncodingHelper.IsCopyCodec(videoCodec) && state.EnableBreakOnNonKeyFrames(videoCodec))
{
- return "-codec:a:0 copy -strict -2 -copypriorss:a:0 0" + bitStreamArgs;
+ return copyArgs + " -copypriorss:a:0 0";
}
- return "-codec:a:0 copy -strict -2" + bitStreamArgs;
+ return copyArgs;
}
- var args = "-codec:a:0 " + audioCodec;
+ var args = "-codec:a:0 " + audioCodec + strictArgs;
var channels = state.OutputAudioChannels;
@@ -1773,13 +1776,25 @@ namespace Jellyfin.Api.Controllers
var args = "-codec:v:0 " + codec;
- // Prefer hvc1 to hev1.
if (string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
{
- args += " -tag:v:0 hvc1";
+ if (EncodingHelper.IsCopyCodec(codec)
+ && (string.Equals(state.VideoStream.VideoRangeType, "DOVI", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(state.VideoStream.CodecTag, "dovi", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(state.VideoStream.CodecTag, "dvh1", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(state.VideoStream.CodecTag, "dvhe", StringComparison.OrdinalIgnoreCase)))
+ {
+ // Prefer dvh1 to dvhe
+ args += " -tag:v:0 dvh1 -strict -2";
+ }
+ else
+ {
+ // Prefer hvc1 to hev1
+ args += " -tag:v:0 hvc1";
+ }
}
// if (state.EnableMpegtsM2TsMode)
@@ -1809,7 +1824,7 @@ namespace Jellyfin.Api.Controllers
// Set the key frame params for video encoding to match the hls segment time.
args += _encodingHelper.GetHlsVideoKeyFrameArguments(state, codec, state.SegmentLength, isEventPlaylist, startNumber);
- // Currenly b-frames in libx265 breaks the FMP4-HLS playback on iOS, disable it for now.
+ // Currently b-frames in libx265 breaks the FMP4-HLS playback on iOS, disable it for now.
if (string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase))
{
args += " -bf 0";
diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs
index e28a50750..611643bd8 100644
--- a/Jellyfin.Api/Controllers/GenresController.cs
+++ b/Jellyfin.Api/Controllers/GenresController.cs
@@ -92,7 +92,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool enableTotalRecordCount = true)
{
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes);
User? user = userId is null || userId.Value.Equals(default)
@@ -157,7 +157,7 @@ namespace Jellyfin.Api.Controllers
public ActionResult<BaseItemDto> GetGenre([FromRoute, Required] string genreName, [FromQuery] Guid? userId)
{
var dtoOptions = new DtoOptions()
- .AddClientFields(Request);
+ .AddClientFields(User);
Genre? item;
if (genreName.Contains(BaseItem.SlugChar, StringComparison.OrdinalIgnoreCase))
diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs
index 05d80ba35..f092bd882 100644
--- a/Jellyfin.Api/Controllers/ImageController.cs
+++ b/Jellyfin.Api/Controllers/ImageController.cs
@@ -17,7 +17,6 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Branding;
using MediaBrowser.Model.Drawing;
@@ -44,11 +43,9 @@ namespace Jellyfin.Api.Controllers
private readonly IProviderManager _providerManager;
private readonly IImageProcessor _imageProcessor;
private readonly IFileSystem _fileSystem;
- private readonly IAuthorizationContext _authContext;
private readonly ILogger<ImageController> _logger;
private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IApplicationPaths _appPaths;
- private readonly IImageEncoder _imageEncoder;
/// <summary>
/// Initializes a new instance of the <see cref="ImageController"/> class.
@@ -58,33 +55,27 @@ namespace Jellyfin.Api.Controllers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="imageProcessor">Instance of the <see cref="IImageProcessor"/> interface.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
- /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{ImageController}"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
- /// <param name="imageEncoder">Instance of the <see cref="IImageEncoder"/> interface.</param>
public ImageController(
IUserManager userManager,
ILibraryManager libraryManager,
IProviderManager providerManager,
IImageProcessor imageProcessor,
IFileSystem fileSystem,
- IAuthorizationContext authContext,
ILogger<ImageController> logger,
IServerConfigurationManager serverConfigurationManager,
- IApplicationPaths appPaths,
- IImageEncoder imageEncoder)
+ IApplicationPaths appPaths)
{
_userManager = userManager;
_libraryManager = libraryManager;
_providerManager = providerManager;
_imageProcessor = imageProcessor;
_fileSystem = fileSystem;
- _authContext = authContext;
_logger = logger;
_serverConfigurationManager = serverConfigurationManager;
_appPaths = appPaths;
- _imageEncoder = imageEncoder;
}
/// <summary>
@@ -108,7 +99,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] ImageType imageType,
[FromQuery] int? index = null)
{
- if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false))
+ if (!RequestHelpers.AssertCanUpdateUser(_userManager, HttpContext.User, userId, true))
{
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image.");
}
@@ -155,7 +146,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] ImageType imageType,
[FromRoute] int index)
{
- if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false))
+ if (!RequestHelpers.AssertCanUpdateUser(_userManager, HttpContext.User, userId, true))
{
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image.");
}
@@ -201,7 +192,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] ImageType imageType,
[FromQuery] int? index = null)
{
- if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false))
+ if (!RequestHelpers.AssertCanUpdateUser(_userManager, HttpContext.User, userId, true))
{
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to delete the image.");
}
@@ -245,7 +236,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] ImageType imageType,
[FromRoute] int index)
{
- if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false))
+ if (!RequestHelpers.AssertCanUpdateUser(_userManager, HttpContext.User, userId, true))
{
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to delete the image.");
}
@@ -1724,6 +1715,11 @@ namespace Jellyfin.Api.Controllers
[FromQuery, Range(0, 100)] int quality = 90)
{
var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding");
+ if (!brandingOptions.SplashscreenEnabled)
+ {
+ return NotFound();
+ }
+
string splashscreenPath;
if (!string.IsNullOrWhiteSpace(brandingOptions.SplashscreenLocation)
@@ -1776,6 +1772,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Uploads a custom splashscreen.
+ /// The body is expected to the image contents base64 encoded.
/// </summary>
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
/// <response code="204">Successfully uploaded new splashscreen.</response>
@@ -1799,7 +1796,13 @@ namespace Jellyfin.Api.Controllers
return BadRequest("Error reading mimetype from uploaded image");
}
- var filePath = Path.Combine(_appPaths.DataPath, "splashscreen-upload" + MimeTypes.ToExtension(mimeType.Value));
+ var extension = MimeTypes.ToExtension(mimeType.Value);
+ if (string.IsNullOrEmpty(extension))
+ {
+ return BadRequest("Error converting mimetype to an image extension");
+ }
+
+ var filePath = Path.Combine(_appPaths.DataPath, "splashscreen-upload" + extension);
var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding");
brandingOptions.SplashscreenLocation = filePath;
_serverConfigurationManager.SaveConfiguration("branding", brandingOptions);
@@ -1812,6 +1815,29 @@ namespace Jellyfin.Api.Controllers
return NoContent();
}
+ /// <summary>
+ /// Delete a custom splashscreen.
+ /// </summary>
+ /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
+ /// <response code="204">Successfully deleted the custom splashscreen.</response>
+ /// <response code="403">User does not have permission to delete splashscreen..</response>
+ [HttpDelete("Branding/Splashscreen")]
+ [Authorize(Policy = Policies.RequiresElevation)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ public ActionResult DeleteCustomSplashscreen()
+ {
+ var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding");
+ if (!string.IsNullOrEmpty(brandingOptions.SplashscreenLocation)
+ && System.IO.File.Exists(brandingOptions.SplashscreenLocation))
+ {
+ System.IO.File.Delete(brandingOptions.SplashscreenLocation);
+ brandingOptions.SplashscreenLocation = null;
+ _serverConfigurationManager.SaveConfiguration("branding", brandingOptions);
+ }
+
+ return NoContent();
+ }
+
private static async Task<MemoryStream> GetMemoryStream(Stream inputStream)
{
using var reader = new StreamReader(inputStream);
diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs
index 9abea5938..2e0d3cb99 100644
--- a/Jellyfin.Api/Controllers/InstantMixController.cs
+++ b/Jellyfin.Api/Controllers/InstantMixController.cs
@@ -79,7 +79,7 @@ namespace Jellyfin.Api.Controllers
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
return GetResult(items, user, limit, dtoOptions);
@@ -115,7 +115,7 @@ namespace Jellyfin.Api.Controllers
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
var items = _musicManager.GetInstantMixFromItem(album, user, dtoOptions);
return GetResult(items, user, limit, dtoOptions);
@@ -151,7 +151,7 @@ namespace Jellyfin.Api.Controllers
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
var items = _musicManager.GetInstantMixFromItem(playlist, user, dtoOptions);
return GetResult(items, user, limit, dtoOptions);
@@ -186,7 +186,7 @@ namespace Jellyfin.Api.Controllers
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
var items = _musicManager.GetInstantMixFromGenres(new[] { name }, user, dtoOptions);
return GetResult(items, user, limit, dtoOptions);
@@ -222,7 +222,7 @@ namespace Jellyfin.Api.Controllers
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
return GetResult(items, user, limit, dtoOptions);
@@ -258,7 +258,7 @@ namespace Jellyfin.Api.Controllers
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
return GetResult(items, user, limit, dtoOptions);
@@ -331,7 +331,7 @@ namespace Jellyfin.Api.Controllers
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
return GetResult(items, user, limit, dtoOptions);
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index 2794a06f3..80ae5abcb 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -1,6 +1,7 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
+using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
@@ -63,7 +64,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Gets items based on a query.
/// </summary>
- /// <param name="userId">The user id supplied as query parameter.</param>
+ /// <param name="userId">The user id supplied as query parameter; this is required when not using an API key.</param>
/// <param name="maxOfficialRating">Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).</param>
/// <param name="hasThemeSong">Optional filter by items with theme songs.</param>
/// <param name="hasThemeVideo">Optional filter by items with theme videos.</param>
@@ -89,6 +90,11 @@ namespace Jellyfin.Api.Controllers
/// <param name="hasImdbId">Optional filter by items that have an imdb id or not.</param>
/// <param name="hasTmdbId">Optional filter by items that have a tmdb id or not.</param>
/// <param name="hasTvdbId">Optional filter by items that have a tvdb id or not.</param>
+ /// <param name="isMovie">Optional filter for live tv movies.</param>
+ /// <param name="isSeries">Optional filter for live tv series.</param>
+ /// <param name="isNews">Optional filter for live tv news.</param>
+ /// <param name="isKids">Optional filter for live tv kids.</param>
+ /// <param name="isSports">Optional filter for live tv sports.</param>
/// <param name="excludeItemIds">Optional. If specified, results will be filtered by excluding item ids. This allows multiple, comma delimited.</param>
/// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param>
@@ -147,14 +153,14 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Items")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetItems(
- [FromQuery] Guid userId,
+ [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] Guid? adjacentTo,
[FromQuery] int? parentIndexNumber,
[FromQuery] bool? hasParentalRating,
[FromQuery] bool? isHd,
@@ -173,6 +179,11 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? hasImdbId,
[FromQuery] bool? hasTmdbId,
[FromQuery] bool? hasTvdbId,
+ [FromQuery] bool? isMovie,
+ [FromQuery] bool? isSeries,
+ [FromQuery] bool? isNews,
+ [FromQuery] bool? isKids,
+ [FromQuery] bool? isSports,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeItemIds,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
@@ -228,9 +239,20 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool enableTotalRecordCount = true,
[FromQuery] bool? enableImages = true)
{
- var user = userId.Equals(default) ? null : _userManager.GetUserById(userId);
+ var isApiKey = User.GetIsApiKey();
+ // if api key is used (auth.IsApiKey == true), then `user` will be null throughout this method
+ var user = !isApiKey && userId.HasValue && !userId.Value.Equals(default)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+
+ // beyond this point, we're either using an api key or we have a valid user
+ if (!isApiKey && user is null)
+ {
+ return BadRequest("userId is required");
+ }
+
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
if (includeItemTypes.Length == 1
@@ -260,30 +282,39 @@ namespace Jellyfin.Api.Controllers
includeItemTypes = new[] { BaseItemKind.Playlist };
}
- var enabledChannels = user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels);
+ var enabledChannels = isApiKey
+ ? Array.Empty<Guid>()
+ : user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels);
- bool isInEnabledFolder = Array.IndexOf(user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders), item.Id) != -1
+ // api keys are always enabled for all folders
+ bool isInEnabledFolder = isApiKey
+ || Array.IndexOf(user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders), item.Id) != -1
// Assume all folders inside an EnabledChannel are enabled
|| Array.IndexOf(enabledChannels, item.Id) != -1
// Assume all items inside an EnabledChannel are enabled
|| Array.IndexOf(enabledChannels, item.ChannelId) != -1;
- var collectionFolders = _libraryManager.GetCollectionFolders(item);
- foreach (var collectionFolder in collectionFolders)
+ if (!isInEnabledFolder)
{
- if (user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders).Contains(collectionFolder.Id))
+ var collectionFolders = _libraryManager.GetCollectionFolders(item);
+ foreach (var collectionFolder in collectionFolders)
{
- isInEnabledFolder = true;
+ // api keys never enter this block, so user is never null
+ if (user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders).Contains(collectionFolder.Id))
+ {
+ isInEnabledFolder = true;
+ }
}
}
+ // api keys are always enabled for all folders, so user is never null
if (item is not UserRootFolder
&& !isInEnabledFolder
- && !user.HasPermission(PermissionKind.EnableAllFolders)
+ && !user!.HasPermission(PermissionKind.EnableAllFolders)
&& !user.HasPermission(PermissionKind.EnableAllChannels)
&& !string.Equals(collectionType, CollectionType.Folders, StringComparison.OrdinalIgnoreCase))
{
- _logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Username, item.Name);
+ _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}.");
}
@@ -316,6 +347,11 @@ namespace Jellyfin.Api.Controllers
Is3D = is3D,
HasTvdbId = hasTvdbId,
HasTmdbId = hasTmdbId,
+ IsMovie = isMovie,
+ IsSeries = isSeries,
+ IsNews = isNews,
+ IsKids = isKids,
+ IsSports = isSports,
HasOverview = hasOverview,
HasOfficialRating = hasOfficialRating,
HasParentalRating = hasParentalRating,
@@ -515,8 +551,8 @@ namespace Jellyfin.Api.Controllers
/// <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="locationTypes">Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimited.</param>
+ /// <param name="excludeLocationTypes">Optional. If specified, results will be filtered based on the LocationType. This allows multiple, comma delimited.</param>
/// <param name="isMissing">Optional filter by items that are missing episodes or not.</param>
/// <param name="isUnaired">Optional filter by items that are unaired episodes or not.</param>
/// <param name="minCommunityRating">Optional filter by minimum community rating.</param>
@@ -529,42 +565,47 @@ namespace Jellyfin.Api.Controllers
/// <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="isMovie">Optional filter for live tv movies.</param>
+ /// <param name="isSeries">Optional filter for live tv series.</param>
+ /// <param name="isNews">Optional filter for live tv news.</param>
+ /// <param name="isKids">Optional filter for live tv kids.</param>
+ /// <param name="isSports">Optional filter for live tv sports.</param>
+ /// <param name="excludeItemIds">Optional. If specified, results will be filtered by excluding item ids. This allows multiple, comma delimited.</param>
/// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param>
/// <param name="recursive">When searching within folders, this determines whether or not the search will be recursive. true/false.</param>
/// <param name="searchTerm">Optional. Filter based on a search term.</param>
/// <param name="sortOrder">Sort Order - Ascending,Descending.</param>
/// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
- /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma 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="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
+ /// <param name="excludeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param>
+ /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimited.</param>
+ /// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.</param>
/// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param>
/// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param>
/// <param name="imageTypes">Optional. If specified, results will be filtered based on those containing image types. This allows multiple, comma delimited.</param>
- /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimeted. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.</param>
+ /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.</param>
/// <param name="isPlayed">Optional filter by items that are played, or not.</param>
- /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe 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="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param>
+ /// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited.</param>
+ /// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited.</param>
+ /// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited.</param>
/// <param name="enableUserData">Optional, include user data.</param>
/// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param>
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
/// <param name="person">Optional. If specified, results will be filtered to include only those containing the specified person.</param>
/// <param name="personIds">Optional. If specified, results will be filtered to include only those containing the specified person id.</param>
/// <param name="personTypes">Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited.</param>
- /// <param name="studios">Optional. If specified, results will be filtered based on studio. This allows multiple, pipe 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="studios">Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited.</param>
+ /// <param name="artists">Optional. If specified, results will be filtered based on artists. This allows multiple, pipe delimited.</param>
+ /// <param name="excludeArtistIds">Optional. If specified, results will be filtered based on artist id. This allows multiple, pipe delimited.</param>
/// <param name="artistIds">Optional. If specified, results will be filtered to include only those containing the specified artist id.</param>
/// <param name="albumArtistIds">Optional. If specified, results will be filtered to include only those containing the specified album artist id.</param>
/// <param name="contributingArtistIds">Optional. If specified, results will be filtered to include only those containing the specified contributing artist id.</param>
- /// <param name="albums">Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimeted.</param>
- /// <param name="albumIds">Optional. If specified, results will be filtered based on album id. This allows multiple, pipe delimeted.</param>
+ /// <param name="albums">Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimited.</param>
+ /// <param name="albumIds">Optional. If specified, results will be filtered based on album id. This allows multiple, pipe delimited.</param>
/// <param name="ids">Optional. If specific items are needed, specify a list of item id's to retrieve. This allows multiple, comma delimited.</param>
- /// <param name="videoTypes">Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimeted.</param>
+ /// <param name="videoTypes">Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimited.</param>
/// <param name="minOfficialRating">Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).</param>
/// <param name="isLocked">Optional filter by items that are locked.</param>
/// <param name="isPlaceHolder">Optional filter by items that are placeholders.</param>
@@ -575,12 +616,12 @@ namespace Jellyfin.Api.Controllers
/// <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="seriesStatus">Optional filter by Series Status. Allows multiple, comma delimited.</param>
/// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param>
/// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param>
/// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param>
- /// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimeted.</param>
- /// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimeted.</param>
+ /// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.</param>
+ /// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.</param>
/// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
/// <param name="enableImages">Optional, include image information in output.</param>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns>
@@ -594,7 +635,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? hasSubtitles,
[FromQuery] bool? hasSpecialFeature,
[FromQuery] bool? hasTrailer,
- [FromQuery] string? adjacentTo,
+ [FromQuery] Guid? adjacentTo,
[FromQuery] int? parentIndexNumber,
[FromQuery] bool? hasParentalRating,
[FromQuery] bool? isHd,
@@ -613,6 +654,11 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? hasImdbId,
[FromQuery] bool? hasTmdbId,
[FromQuery] bool? hasTvdbId,
+ [FromQuery] bool? isMovie,
+ [FromQuery] bool? isSeries,
+ [FromQuery] bool? isNews,
+ [FromQuery] bool? isKids,
+ [FromQuery] bool? isSports,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeItemIds,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
@@ -695,6 +741,11 @@ namespace Jellyfin.Api.Controllers
hasImdbId,
hasTmdbId,
hasTvdbId,
+ isMovie,
+ isSeries,
+ isNews,
+ isKids,
+ isSports,
excludeItemIds,
startIndex,
limit,
@@ -793,7 +844,7 @@ namespace Jellyfin.Api.Controllers
var user = _userManager.GetUserById(userId);
var parentIdGuid = parentId ?? Guid.Empty;
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
var ancestorIds = Array.Empty<Guid>();
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index 4cc17dd0f..e9492a6a4 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -24,7 +24,6 @@ using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Configuration;
@@ -50,7 +49,6 @@ namespace Jellyfin.Api.Controllers
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
private readonly IDtoService _dtoService;
- private readonly IAuthorizationContext _authContext;
private readonly IActivityManager _activityManager;
private readonly ILocalizationManager _localization;
private readonly ILibraryMonitor _libraryMonitor;
@@ -64,7 +62,6 @@ namespace Jellyfin.Api.Controllers
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
- /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
/// <param name="activityManager">Instance of the <see cref="IActivityManager"/> interface.</param>
/// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
/// <param name="libraryMonitor">Instance of the <see cref="ILibraryMonitor"/> interface.</param>
@@ -75,7 +72,6 @@ namespace Jellyfin.Api.Controllers
ILibraryManager libraryManager,
IUserManager userManager,
IDtoService dtoService,
- IAuthorizationContext authContext,
IActivityManager activityManager,
ILocalizationManager localization,
ILibraryMonitor libraryMonitor,
@@ -86,7 +82,6 @@ namespace Jellyfin.Api.Controllers
_libraryManager = libraryManager;
_userManager = userManager;
_dtoService = dtoService;
- _authContext = authContext;
_activityManager = activityManager;
_localization = localization;
_libraryMonitor = libraryMonitor;
@@ -184,7 +179,7 @@ namespace Jellyfin.Api.Controllers
item = parent;
}
- var dtoOptions = new DtoOptions().AddClientFields(Request);
+ var dtoOptions = new DtoOptions().AddClientFields(User);
var items = themeItems
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
.ToArray();
@@ -250,7 +245,7 @@ namespace Jellyfin.Api.Controllers
item = parent;
}
- var dtoOptions = new DtoOptions().AddClientFields(Request);
+ var dtoOptions = new DtoOptions().AddClientFields(User);
var items = themeItems
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
.ToArray();
@@ -331,11 +326,10 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
- public async Task<ActionResult> DeleteItem(Guid itemId)
+ public ActionResult DeleteItem(Guid itemId)
{
var item = _libraryManager.GetItemById(itemId);
- var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
- var user = auth.User;
+ var user = _userManager.GetUserById(User.GetUserId());
if (!item.CanDelete(user))
{
@@ -361,7 +355,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
- public async Task<ActionResult> DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids)
+ public ActionResult DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids)
{
if (ids.Length == 0)
{
@@ -371,8 +365,7 @@ namespace Jellyfin.Api.Controllers
foreach (var i in ids)
{
var item = _libraryManager.GetItemById(i);
- var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
- var user = auth.User;
+ var user = _userManager.GetUserById(User.GetUserId());
if (!item.CanDelete(user))
{
@@ -453,7 +446,7 @@ namespace Jellyfin.Api.Controllers
? null
: _userManager.GetUserById(userId.Value);
- var dtoOptions = new DtoOptions().AddClientFields(Request);
+ var dtoOptions = new DtoOptions().AddClientFields(User);
BaseItem? parent = item.GetParent();
while (parent != null)
@@ -505,7 +498,7 @@ namespace Jellyfin.Api.Controllers
items = items.Where(i => i.IsHidden == val).ToList();
}
- var dtoOptions = new DtoOptions().AddClientFields(Request);
+ var dtoOptions = new DtoOptions().AddClientFields(User);
var resultArray = _dtoService.GetBaseItemDtos(items, dtoOptions);
return new QueryResult<BaseItemDto>(resultArray);
}
@@ -622,9 +615,7 @@ namespace Jellyfin.Api.Controllers
return NotFound();
}
- var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
-
- var user = auth.User;
+ var user = _userManager.GetUserById(User.GetUserId());
if (user != null)
{
@@ -643,7 +634,7 @@ namespace Jellyfin.Api.Controllers
if (user != null)
{
- await LogDownloadAsync(item, user, auth).ConfigureAwait(false);
+ await LogDownloadAsync(item, user).ConfigureAwait(false);
}
var path = item.Path;
@@ -704,7 +695,7 @@ namespace Jellyfin.Api.Controllers
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request);
+ .AddClientFields(User);
var program = item as IHasProgramAttributes;
bool? isMovie = item is Movie || (program != null && program.IsMovie) || item is Trailer;
@@ -892,16 +883,16 @@ namespace Jellyfin.Api.Controllers
: item;
}
- private async Task LogDownloadAsync(BaseItem item, User user, AuthorizationInfo auth)
+ private async Task LogDownloadAsync(BaseItem item, User user)
{
try
{
await _activityManager.CreateAsync(new ActivityLog(
string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Username, item.Name),
"UserDownloadingContent",
- auth.UserId)
+ User.GetUserId())
{
- ShortOverview = string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("AppDeviceValues"), auth.Client, auth.Device),
+ ShortOverview = string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("AppDeviceValues"), User.GetClient(), User.GetDevice()),
}).ConfigureAwait(false);
}
catch
diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
index 05340099b..394df0f58 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -17,6 +17,7 @@ using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.LiveTvDtos;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -24,6 +25,7 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Net;
+using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
@@ -45,10 +47,10 @@ namespace Jellyfin.Api.Controllers
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILibraryManager _libraryManager;
private readonly IDtoService _dtoService;
- private readonly ISessionContext _sessionContext;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IConfigurationManager _configurationManager;
private readonly TranscodingJobHelper _transcodingJobHelper;
+ private readonly ISessionManager _sessionManager;
/// <summary>
/// Initializes a new instance of the <see cref="LiveTvController"/> class.
@@ -58,30 +60,30 @@ namespace Jellyfin.Api.Controllers
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
- /// <param name="sessionContext">Instance of the <see cref="ISessionContext"/> interface.</param>
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
/// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/> class.</param>
+ /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
public LiveTvController(
ILiveTvManager liveTvManager,
IUserManager userManager,
IHttpClientFactory httpClientFactory,
ILibraryManager libraryManager,
IDtoService dtoService,
- ISessionContext sessionContext,
IMediaSourceManager mediaSourceManager,
IConfigurationManager configurationManager,
- TranscodingJobHelper transcodingJobHelper)
+ TranscodingJobHelper transcodingJobHelper,
+ ISessionManager sessionManager)
{
_liveTvManager = liveTvManager;
_userManager = userManager;
_httpClientFactory = httpClientFactory;
_libraryManager = libraryManager;
_dtoService = dtoService;
- _sessionContext = sessionContext;
_mediaSourceManager = mediaSourceManager;
_configurationManager = configurationManager;
_transcodingJobHelper = transcodingJobHelper;
+ _sessionManager = sessionManager;
}
/// <summary>
@@ -154,7 +156,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool addCurrentProgram = true)
{
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
var channelResult = _liveTvManager.GetInternalChannels(
@@ -219,7 +221,7 @@ namespace Jellyfin.Api.Controllers
: _libraryManager.GetItemById(channelId);
var dtoOptions = new DtoOptions()
- .AddClientFields(Request);
+ .AddClientFields(User);
return _dtoService.GetBaseItemDto(item, dtoOptions, user);
}
@@ -272,7 +274,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool enableTotalRecordCount = true)
{
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
return _liveTvManager.GetRecordings(
@@ -410,7 +412,7 @@ namespace Jellyfin.Api.Controllers
var item = recordingId.Equals(default) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(recordingId);
var dtoOptions = new DtoOptions()
- .AddClientFields(Request);
+ .AddClientFields(User);
return _dtoService.GetBaseItemDto(item, dtoOptions, user);
}
@@ -599,7 +601,7 @@ namespace Jellyfin.Api.Controllers
}
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
return await _liveTvManager.GetPrograms(query, dtoOptions, CancellationToken.None).ConfigureAwait(false);
}
@@ -653,7 +655,7 @@ namespace Jellyfin.Api.Controllers
}
var dtoOptions = new DtoOptions { Fields = body.Fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(body.EnableImages, body.EnableUserData, body.ImageTypeLimit, body.EnableImageTypes);
return await _liveTvManager.GetPrograms(query, dtoOptions, CancellationToken.None).ConfigureAwait(false);
}
@@ -719,7 +721,7 @@ namespace Jellyfin.Api.Controllers
};
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
return await _liveTvManager.GetRecommendedProgramsAsync(query, dtoOptions, CancellationToken.None).ConfigureAwait(false);
}
@@ -1210,9 +1212,16 @@ namespace Jellyfin.Api.Controllers
private async Task AssertUserCanManageLiveTv()
{
- var user = await _sessionContext.GetUser(Request).ConfigureAwait(false);
-
- if (user == null)
+ var user = _userManager.GetUserById(User.GetUserId());
+ var session = await _sessionManager.LogSessionActivity(
+ User.GetClient(),
+ User.GetVersion(),
+ User.GetDeviceId(),
+ User.GetDevice(),
+ HttpContext.GetNormalizedRemoteIp().ToString(),
+ user).ConfigureAwait(false);
+
+ if (session.UserId.Equals(default))
{
throw new SecurityException("Anonymous live tv management is not allowed.");
}
diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs
index 75df18204..c111e9218 100644
--- a/Jellyfin.Api/Controllers/MediaInfoController.cs
+++ b/Jellyfin.Api/Controllers/MediaInfoController.cs
@@ -6,13 +6,12 @@ using System.Net.Mime;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
+using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.MediaInfoDtos;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.MediaInfo;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
@@ -32,7 +31,6 @@ namespace Jellyfin.Api.Controllers
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IDeviceManager _deviceManager;
private readonly ILibraryManager _libraryManager;
- private readonly IAuthorizationContext _authContext;
private readonly ILogger<MediaInfoController> _logger;
private readonly MediaInfoHelper _mediaInfoHelper;
@@ -42,21 +40,18 @@ namespace Jellyfin.Api.Controllers
/// <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="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{MediaInfoController}"/> interface.</param>
/// <param name="mediaInfoHelper">Instance of the <see cref="MediaInfoHelper"/>.</param>
public MediaInfoController(
IMediaSourceManager mediaSourceManager,
IDeviceManager deviceManager,
ILibraryManager libraryManager,
- IAuthorizationContext authContext,
ILogger<MediaInfoController> logger,
MediaInfoHelper mediaInfoHelper)
{
_mediaSourceManager = mediaSourceManager;
_deviceManager = deviceManager;
_libraryManager = libraryManager;
- _authContext = authContext;
_logger = logger;
_mediaInfoHelper = mediaInfoHelper;
}
@@ -123,14 +118,12 @@ namespace Jellyfin.Api.Controllers
[FromQuery, ParameterObsolete] bool? allowAudioStreamCopy,
[FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] PlaybackInfoDto? playbackInfoDto)
{
- var authInfo = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
-
var profile = playbackInfoDto?.DeviceProfile;
_logger.LogDebug("GetPostedPlaybackInfo profile: {@Profile}", profile);
if (profile == null)
{
- var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
+ var caps = _deviceManager.GetCapabilities(User.GetDeviceId());
if (caps != null)
{
profile = caps.DeviceProfile;
@@ -177,7 +170,7 @@ namespace Jellyfin.Api.Controllers
item,
mediaSource,
profile,
- authInfo,
+ User,
maxStreamingBitrate ?? profile.MaxStreamingBitrate,
startTimeTicks ?? 0,
mediaSourceId ?? string.Empty,
@@ -204,7 +197,7 @@ namespace Jellyfin.Api.Controllers
if (mediaSource != null && mediaSource.RequiresOpening && string.IsNullOrWhiteSpace(mediaSource.LiveStreamId))
{
var openStreamResult = await _mediaInfoHelper.OpenMediaSource(
- Request,
+ HttpContext,
new LiveStreamRequest
{
AudioStreamIndex = audioStreamIndex,
@@ -277,7 +270,7 @@ namespace Jellyfin.Api.Controllers
EnableDirectStream = enableDirectStream ?? openLiveStreamDto?.EnableDirectStream ?? true,
DirectPlayProtocols = openLiveStreamDto?.DirectPlayProtocols ?? new[] { MediaProtocol.Http }
};
- return await _mediaInfoHelper.OpenMediaSource(Request, request).ConfigureAwait(false);
+ return await _mediaInfoHelper.OpenMediaSource(HttpContext, request).ConfigureAwait(false);
}
/// <summary>
diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs
index 420dd9923..8195fc760 100644
--- a/Jellyfin.Api/Controllers/MoviesController.cs
+++ b/Jellyfin.Api/Controllers/MoviesController.cs
@@ -72,7 +72,7 @@ namespace Jellyfin.Api.Controllers
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request);
+ .AddClientFields(User);
var categories = new List<RecommendationDto>();
@@ -170,7 +170,7 @@ namespace Jellyfin.Api.Controllers
}
}
- return Ok(categories.OrderBy(i => i.RecommendationType));
+ return Ok(categories.OrderBy(i => i.RecommendationType).AsEnumerable());
}
private IEnumerable<RecommendationDto> GetWithDirector(
diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs
index 0499b2985..f4fb5f44a 100644
--- a/Jellyfin.Api/Controllers/MusicGenresController.cs
+++ b/Jellyfin.Api/Controllers/MusicGenresController.cs
@@ -92,7 +92,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool enableTotalRecordCount = true)
{
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes);
User? user = userId is null || userId.Value.Equals(default)
@@ -145,7 +145,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<BaseItemDto> GetMusicGenre([FromRoute, Required] string genreName, [FromQuery] Guid? userId)
{
- var dtoOptions = new DtoOptions().AddClientFields(Request);
+ var dtoOptions = new DtoOptions().AddClientFields(User);
MusicGenre? item;
diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs
index 5dd49ef2f..9690aead0 100644
--- a/Jellyfin.Api/Controllers/PackageController.cs
+++ b/Jellyfin.Api/Controllers/PackageController.cs
@@ -155,7 +155,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Package repositories saved.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Repositories")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SetRepositories([FromBody, Required] List<RepositoryInfo> repositoryInfos)
{
diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs
index be4b9eded..42be969b2 100644
--- a/Jellyfin.Api/Controllers/PersonsController.cs
+++ b/Jellyfin.Api/Controllers/PersonsController.cs
@@ -79,7 +79,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? enableImages = true)
{
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
User? user = userId is null || userId.Value.Equals(default)
@@ -98,7 +98,10 @@ namespace Jellyfin.Api.Controllers
Limit = limit ?? 0
});
- return new QueryResult<BaseItemDto>(peopleItems.Select(person => _dtoService.GetItemByNameDto(person, dtoOptions, null, user)).ToArray());
+ return new QueryResult<BaseItemDto>(
+ peopleItems
+ .Select(person => _dtoService.GetItemByNameDto(person, dtoOptions, null, user))
+ .ToArray());
}
/// <summary>
@@ -116,7 +119,7 @@ namespace Jellyfin.Api.Controllers
public ActionResult<BaseItemDto> GetPerson([FromRoute, Required] string name, [FromQuery] Guid? userId)
{
var dtoOptions = new DtoOptions()
- .AddClientFields(Request);
+ .AddClientFields(User);
var item = _libraryManager.GetPerson(name);
if (item == null)
diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs
index ad85f2fb2..fb045f891 100644
--- a/Jellyfin.Api/Controllers/PlaylistsController.cs
+++ b/Jellyfin.Api/Controllers/PlaylistsController.cs
@@ -200,7 +200,7 @@ namespace Jellyfin.Api.Controllers
}
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
var dtos = _dtoService.GetBaseItemDtos(items.Select(i => i.Item2).ToList(), dtoOptions, user);
diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs
index 6dee1c219..0dd4bf803 100644
--- a/Jellyfin.Api/Controllers/PlaystateController.cs
+++ b/Jellyfin.Api/Controllers/PlaystateController.cs
@@ -3,11 +3,11 @@ using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
+using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Session;
@@ -29,7 +29,6 @@ namespace Jellyfin.Api.Controllers
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;
@@ -40,7 +39,6 @@ namespace Jellyfin.Api.Controllers
/// <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="transcodingJobHelper">Th <see cref="TranscodingJobHelper"/> singleton.</param>
public PlaystateController(
@@ -48,7 +46,6 @@ namespace Jellyfin.Api.Controllers
IUserDataManager userDataRepository,
ILibraryManager libraryManager,
ISessionManager sessionManager,
- IAuthorizationContext authContext,
ILoggerFactory loggerFactory,
TranscodingJobHelper transcodingJobHelper)
{
@@ -56,7 +53,6 @@ namespace Jellyfin.Api.Controllers
_userDataRepository = userDataRepository;
_libraryManager = libraryManager;
_sessionManager = sessionManager;
- _authContext = authContext;
_logger = loggerFactory.CreateLogger<PlaystateController>();
_transcodingJobHelper = transcodingJobHelper;
@@ -78,7 +74,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery, ModelBinder(typeof(LegacyDateTimeModelBinder))] DateTime? datePlayed)
{
var user = _userManager.GetUserById(userId);
- var session = await RequestHelpers.GetSession(_sessionManager, _authContext, Request).ConfigureAwait(false);
+ var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var dto = UpdatePlayedStatus(user, itemId, true, datePlayed);
foreach (var additionalUserInfo in session.AdditionalUsers)
{
@@ -101,7 +97,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult<UserItemDataDto>> MarkUnplayedItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{
var user = _userManager.GetUserById(userId);
- var session = await RequestHelpers.GetSession(_sessionManager, _authContext, Request).ConfigureAwait(false);
+ var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var dto = UpdatePlayedStatus(user, itemId, false, null);
foreach (var additionalUserInfo in session.AdditionalUsers)
{
@@ -123,7 +119,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> ReportPlaybackStart([FromBody] PlaybackStartInfo playbackStartInfo)
{
playbackStartInfo.PlayMethod = ValidatePlayMethod(playbackStartInfo.PlayMethod, playbackStartInfo.PlaySessionId);
- playbackStartInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false);
+ playbackStartInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
await _sessionManager.OnPlaybackStart(playbackStartInfo).ConfigureAwait(false);
return NoContent();
}
@@ -139,7 +135,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> ReportPlaybackProgress([FromBody] PlaybackProgressInfo playbackProgressInfo)
{
playbackProgressInfo.PlayMethod = ValidatePlayMethod(playbackProgressInfo.PlayMethod, playbackProgressInfo.PlaySessionId);
- playbackProgressInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false);
+ playbackProgressInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
await _sessionManager.OnPlaybackProgress(playbackProgressInfo).ConfigureAwait(false);
return NoContent();
}
@@ -171,11 +167,10 @@ namespace Jellyfin.Api.Controllers
_logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty);
if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId))
{
- var authInfo = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
- await _transcodingJobHelper.KillTranscodingJobs(authInfo.DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false);
+ await _transcodingJobHelper.KillTranscodingJobs(User.GetDeviceId()!, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false);
}
- playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false);
+ playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
await _sessionManager.OnPlaybackStopped(playbackStopInfo).ConfigureAwait(false);
return NoContent();
}
@@ -221,7 +216,7 @@ namespace Jellyfin.Api.Controllers
};
playbackStartInfo.PlayMethod = ValidatePlayMethod(playbackStartInfo.PlayMethod, playbackStartInfo.PlaySessionId);
- playbackStartInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false);
+ playbackStartInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
await _sessionManager.OnPlaybackStart(playbackStartInfo).ConfigureAwait(false);
return NoContent();
}
@@ -279,7 +274,7 @@ namespace Jellyfin.Api.Controllers
};
playbackProgressInfo.PlayMethod = ValidatePlayMethod(playbackProgressInfo.PlayMethod, playbackProgressInfo.PlaySessionId);
- playbackProgressInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false);
+ playbackProgressInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);;
await _sessionManager.OnPlaybackProgress(playbackProgressInfo).ConfigureAwait(false);
return NoContent();
}
@@ -321,11 +316,10 @@ namespace Jellyfin.Api.Controllers
_logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty);
if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId))
{
- var authInfo = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
- await _transcodingJobHelper.KillTranscodingJobs(authInfo.DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false);
+ await _transcodingJobHelper.KillTranscodingJobs(User.GetDeviceId()!, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false);
}
- playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false);
+ playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);;
await _sessionManager.OnPlaybackStopped(playbackStopInfo).ConfigureAwait(false);
return NoContent();
}
diff --git a/Jellyfin.Api/Controllers/QuickConnectController.cs b/Jellyfin.Api/Controllers/QuickConnectController.cs
index 87b78fe93..77d88475f 100644
--- a/Jellyfin.Api/Controllers/QuickConnectController.cs
+++ b/Jellyfin.Api/Controllers/QuickConnectController.cs
@@ -1,6 +1,7 @@
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
+using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Authentication;
@@ -39,7 +40,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>Whether Quick Connect is enabled on the server or not.</returns>
[HttpGet("Enabled")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<bool> GetEnabled()
+ public ActionResult<bool> GetQuickConnectEnabled()
{
return _quickConnect.IsEnabled;
}
@@ -52,7 +53,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="QuickConnectResult"/> with a secret and code for future use or an error message.</returns>
[HttpGet("Initiate")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public async Task<ActionResult<QuickConnectResult>> Initiate()
+ public async Task<ActionResult<QuickConnectResult>> InitiateQuickConnect()
{
try
{
@@ -75,7 +76,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Connect")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult<QuickConnectResult> Connect([FromQuery, Required] string secret)
+ public ActionResult<QuickConnectResult> GetQuickConnectState([FromQuery, Required] string secret)
{
try
{
@@ -102,17 +103,17 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
- public async Task<ActionResult<bool>> Authorize([FromQuery, Required] string code)
+ public async Task<ActionResult<bool>> AuthorizeQuickConnect([FromQuery, Required] string code)
{
- var userId = ClaimHelpers.GetUserId(Request.HttpContext.User);
- if (!userId.HasValue)
+ var userId = User.GetUserId();
+ if (userId.Equals(default))
{
return StatusCode(StatusCodes.Status403Forbidden, "Unknown user id");
}
try
{
- return await _quickConnect.AuthorizeRequest(userId.Value, code).ConfigureAwait(false);
+ return await _quickConnect.AuthorizeRequest(userId, code).ConfigureAwait(false);
}
catch (AuthenticationException)
{
diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs
index 6ffedccbd..aeed0c0d6 100644
--- a/Jellyfin.Api/Controllers/SearchController.cs
+++ b/Jellyfin.Api/Controllers/SearchController.cs
@@ -6,6 +6,7 @@ using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -59,9 +60,9 @@ namespace Jellyfin.Api.Controllers
/// <param name="limit">Optional. The maximum number of records to return.</param>
/// <param name="userId">Optional. Supply a user id to search within a user's library or omit to search all.</param>
/// <param name="searchTerm">The search term to filter on.</param>
- /// <param name="includeItemTypes">If specified, only results with the specified item types are returned. This allows multiple, comma delimeted.</param>
- /// <param name="excludeItemTypes">If specified, results with these item types are filtered out. This allows multiple, comma delimeted.</param>
- /// <param name="mediaTypes">If specified, only results with the specified media types are returned. This allows multiple, comma delimeted.</param>
+ /// <param name="includeItemTypes">If specified, only results with the specified item types are returned. This allows multiple, comma delimited.</param>
+ /// <param name="excludeItemTypes">If specified, results with these item types are filtered out. This allows multiple, comma delimited.</param>
+ /// <param name="mediaTypes">If specified, only results with the specified media types are returned. This allows multiple, comma delimited.</param>
/// <param name="parentId">If specified, only children of the parent are returned.</param>
/// <param name="isMovie">Optional filter for movies.</param>
/// <param name="isSeries">Optional filter for series.</param>
@@ -78,7 +79,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet]
[Description("Gets search hints based on a search term")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<SearchHintResult> Get(
+ public ActionResult<SearchHintResult> GetSearchHints(
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] Guid? userId,
@@ -139,7 +140,7 @@ namespace Jellyfin.Api.Controllers
IndexNumber = item.IndexNumber,
ParentIndexNumber = item.ParentIndexNumber,
Id = item.Id,
- Type = item.GetClientTypeName(),
+ Type = item.GetBaseItemKind(),
MediaType = item.MediaType,
MatchedTerm = hintInfo.MatchedTerm,
RunTimeTicks = item.RunTimeTicks,
@@ -148,8 +149,10 @@ namespace Jellyfin.Api.Controllers
EndDate = item.EndDate
};
- // legacy
+#pragma warning disable CS0618
+ // Kept for compatibility with older clients
result.ItemId = result.Id;
+#pragma warning restore CS0618
if (item.IsFolder)
{
@@ -187,7 +190,7 @@ namespace Jellyfin.Api.Controllers
result.AlbumArtist = album.AlbumArtist;
break;
case Audio song:
- result.AlbumArtist = song.AlbumArtists?[0];
+ result.AlbumArtist = song.AlbumArtists?.FirstOrDefault();
result.Artists = song.Artists;
MusicAlbum musicAlbum = song.AlbumEntity;
diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs
index 860bccb9b..28415555e 100644
--- a/Jellyfin.Api/Controllers/SessionController.cs
+++ b/Jellyfin.Api/Controllers/SessionController.cs
@@ -5,13 +5,13 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
+using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.SessionDtos;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Session;
@@ -29,7 +29,6 @@ namespace Jellyfin.Api.Controllers
{
private readonly ISessionManager _sessionManager;
private readonly IUserManager _userManager;
- private readonly IAuthorizationContext _authContext;
private readonly IDeviceManager _deviceManager;
/// <summary>
@@ -37,17 +36,14 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <param name="sessionManager">Instance of <see cref="ISessionManager"/> interface.</param>
/// <param name="userManager">Instance of <see cref="IUserManager"/> interface.</param>
- /// <param name="authContext">Instance of <see cref="IAuthorizationContext"/> interface.</param>
/// <param name="deviceManager">Instance of <see cref="IDeviceManager"/> interface.</param>
public SessionController(
ISessionManager sessionManager,
IUserManager userManager,
- IAuthorizationContext authContext,
IDeviceManager deviceManager)
{
_sessionManager = sessionManager;
_userManager = userManager;
- _authContext = authContext;
_deviceManager = deviceManager;
}
@@ -139,7 +135,7 @@ namespace Jellyfin.Api.Controllers
};
await _sessionManager.SendBrowseCommand(
- await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false),
+ await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false),
sessionId,
command,
CancellationToken.None)
@@ -186,7 +182,7 @@ namespace Jellyfin.Api.Controllers
};
await _sessionManager.SendPlayCommand(
- await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false),
+ await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false),
sessionId,
playRequest,
CancellationToken.None)
@@ -214,7 +210,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? controllingUserId)
{
await _sessionManager.SendPlaystateCommand(
- await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false),
+ await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false),
sessionId,
new PlaystateRequest()
{
@@ -242,14 +238,14 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] string sessionId,
[FromRoute, Required] GeneralCommandType command)
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var generalCommand = new GeneralCommand
{
Name = command,
ControllingUserId = currentSession.UserId
};
- await _sessionManager.SendGeneralCommand(currentSession.Id, sessionId, generalCommand, CancellationToken.None);
+ await _sessionManager.SendGeneralCommand(currentSession.Id, sessionId, generalCommand, CancellationToken.None).ConfigureAwait(false);
return NoContent();
}
@@ -268,7 +264,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] string sessionId,
[FromRoute, Required] GeneralCommandType command)
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var generalCommand = new GeneralCommand
{
@@ -296,8 +292,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] string sessionId,
[FromBody, Required] GeneralCommand command)
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authContext, Request)
- .ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
if (command == null)
{
@@ -336,7 +331,7 @@ namespace Jellyfin.Api.Controllers
}
await _sessionManager.SendMessageCommand(
- await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false),
+ await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false),
sessionId,
command,
CancellationToken.None)
@@ -405,7 +400,7 @@ namespace Jellyfin.Api.Controllers
{
if (string.IsNullOrWhiteSpace(id))
{
- id = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false);
+ id = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
}
_sessionManager.ReportCapabilities(id, new ClientCapabilities
@@ -435,7 +430,7 @@ namespace Jellyfin.Api.Controllers
{
if (string.IsNullOrWhiteSpace(id))
{
- id = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false);
+ id = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
}
_sessionManager.ReportCapabilities(id, capabilities.ToClientCapabilities());
@@ -457,7 +452,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? sessionId,
[FromQuery, Required] string? itemId)
{
- string session = sessionId ?? await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false);
+ string session = sessionId ?? await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
_sessionManager.ReportNowViewingItem(session, itemId);
return NoContent();
@@ -473,9 +468,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> ReportSessionEnded()
{
- AuthorizationInfo auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
-
- await _sessionManager.Logout(auth.Token).ConfigureAwait(false);
+ await _sessionManager.Logout(User.GetToken()).ConfigureAwait(false);
return NoContent();
}
diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs
index 053c7baaa..1288fb512 100644
--- a/Jellyfin.Api/Controllers/StudiosController.cs
+++ b/Jellyfin.Api/Controllers/StudiosController.cs
@@ -88,7 +88,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool enableTotalRecordCount = true)
{
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
User? user = userId is null || userId.Value.Equals(default)
@@ -140,7 +140,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<BaseItemDto> GetStudio([FromRoute, Required] string name, [FromQuery] Guid? userId)
{
- var dtoOptions = new DtoOptions().AddClientFields(Request);
+ var dtoOptions = new DtoOptions().AddClientFields(User);
var item = _libraryManager.GetStudio(name);
if (userId.HasValue && !userId.Equals(default))
diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs
index 16acedcf3..1258a9876 100644
--- a/Jellyfin.Api/Controllers/SubtitleController.cs
+++ b/Jellyfin.Api/Controllers/SubtitleController.cs
@@ -11,13 +11,13 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
+using Jellyfin.Api.Extensions;
using Jellyfin.Api.Models.SubtitleDtos;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Subtitles;
using MediaBrowser.Model.Entities;
@@ -45,7 +45,6 @@ namespace Jellyfin.Api.Controllers
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IProviderManager _providerManager;
private readonly IFileSystem _fileSystem;
- private readonly IAuthorizationContext _authContext;
private readonly ILogger<SubtitleController> _logger;
/// <summary>
@@ -58,7 +57,6 @@ namespace Jellyfin.Api.Controllers
/// <param name="mediaSourceManager">Instance of <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="providerManager">Instance of <see cref="IProviderManager"/> interface.</param>
/// <param name="fileSystem">Instance of <see cref="IFileSystem"/> interface.</param>
- /// <param name="authContext">Instance of <see cref="IAuthorizationContext"/> interface.</param>
/// <param name="logger">Instance of <see cref="ILogger{SubtitleController}"/> interface.</param>
public SubtitleController(
IServerConfigurationManager serverConfigurationManager,
@@ -68,7 +66,6 @@ namespace Jellyfin.Api.Controllers
IMediaSourceManager mediaSourceManager,
IProviderManager providerManager,
IFileSystem fileSystem,
- IAuthorizationContext authContext,
ILogger<SubtitleController> logger)
{
_serverConfigurationManager = serverConfigurationManager;
@@ -78,7 +75,6 @@ namespace Jellyfin.Api.Controllers
_mediaSourceManager = mediaSourceManager;
_providerManager = providerManager;
_fileSystem = fileSystem;
- _authContext = authContext;
_logger = logger;
}
@@ -361,7 +357,7 @@ namespace Jellyfin.Api.Controllers
long positionTicks = 0;
- var accessToken = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).Token;
+ var accessToken = User.GetToken();
while (positionTicks < runtime)
{
diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs
index e9c46dcf3..1cf528153 100644
--- a/Jellyfin.Api/Controllers/SuggestionsController.cs
+++ b/Jellyfin.Api/Controllers/SuggestionsController.cs
@@ -67,7 +67,7 @@ namespace Jellyfin.Api.Controllers
? null
: _userManager.GetUserById(userId);
- var dtoOptions = new DtoOptions().AddClientFields(Request);
+ var dtoOptions = new DtoOptions().AddClientFields(User);
var result = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
{
OrderBy = new[] { (ItemSortBy.Random, SortOrder.Descending) },
diff --git a/Jellyfin.Api/Controllers/SyncPlayController.cs b/Jellyfin.Api/Controllers/SyncPlayController.cs
index c6b70f3d2..e194fc556 100644
--- a/Jellyfin.Api/Controllers/SyncPlayController.cs
+++ b/Jellyfin.Api/Controllers/SyncPlayController.cs
@@ -5,7 +5,7 @@ using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.SyncPlayDtos;
-using MediaBrowser.Controller.Net;
+using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Session;
using MediaBrowser.Controller.SyncPlay;
using MediaBrowser.Controller.SyncPlay.PlaybackRequests;
@@ -24,23 +24,23 @@ namespace Jellyfin.Api.Controllers
public class SyncPlayController : BaseJellyfinApiController
{
private readonly ISessionManager _sessionManager;
- private readonly IAuthorizationContext _authorizationContext;
private readonly ISyncPlayManager _syncPlayManager;
+ private readonly IUserManager _userManager;
/// <summary>
/// Initializes a new instance of the <see cref="SyncPlayController"/> class.
/// </summary>
/// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
- /// <param name="authorizationContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
/// <param name="syncPlayManager">Instance of the <see cref="ISyncPlayManager"/> interface.</param>
+ /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
public SyncPlayController(
ISessionManager sessionManager,
- IAuthorizationContext authorizationContext,
- ISyncPlayManager syncPlayManager)
+ ISyncPlayManager syncPlayManager,
+ IUserManager userManager)
{
_sessionManager = sessionManager;
- _authorizationContext = authorizationContext;
_syncPlayManager = syncPlayManager;
+ _userManager = userManager;
}
/// <summary>
@@ -55,7 +55,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> SyncPlayCreateGroup(
[FromBody, Required] NewGroupRequestDto requestData)
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new NewGroupRequest(requestData.GroupName);
_syncPlayManager.NewGroup(currentSession, syncPlayRequest, CancellationToken.None);
return NoContent();
@@ -73,7 +73,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> SyncPlayJoinGroup(
[FromBody, Required] JoinGroupRequestDto requestData)
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new JoinGroupRequest(requestData.GroupId);
_syncPlayManager.JoinGroup(currentSession, syncPlayRequest, CancellationToken.None);
return NoContent();
@@ -89,7 +89,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.SyncPlayIsInGroup)]
public async Task<ActionResult> SyncPlayLeaveGroup()
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new LeaveGroupRequest();
_syncPlayManager.LeaveGroup(currentSession, syncPlayRequest, CancellationToken.None);
return NoContent();
@@ -105,7 +105,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.SyncPlayJoinGroup)]
public async Task<ActionResult<IEnumerable<GroupInfoDto>>> SyncPlayGetGroups()
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new ListGroupsRequest();
return Ok(_syncPlayManager.ListGroups(currentSession, syncPlayRequest));
}
@@ -122,7 +122,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> SyncPlaySetNewQueue(
[FromBody, Required] PlayRequestDto requestData)
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new PlayGroupRequest(
requestData.PlayingQueue,
requestData.PlayingItemPosition,
@@ -143,7 +143,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> SyncPlaySetPlaylistItem(
[FromBody, Required] SetPlaylistItemRequestDto requestData)
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new SetPlaylistItemGroupRequest(requestData.PlaylistItemId);
_syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
return NoContent();
@@ -161,7 +161,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> SyncPlayRemoveFromPlaylist(
[FromBody, Required] RemoveFromPlaylistRequestDto requestData)
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new RemoveFromPlaylistGroupRequest(requestData.PlaylistItemIds, requestData.ClearPlaylist, requestData.ClearPlayingItem);
_syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
return NoContent();
@@ -179,7 +179,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> SyncPlayMovePlaylistItem(
[FromBody, Required] MovePlaylistItemRequestDto requestData)
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new MovePlaylistItemGroupRequest(requestData.PlaylistItemId, requestData.NewIndex);
_syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
return NoContent();
@@ -197,7 +197,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> SyncPlayQueue(
[FromBody, Required] QueueRequestDto requestData)
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new QueueGroupRequest(requestData.ItemIds, requestData.Mode);
_syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
return NoContent();
@@ -213,7 +213,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.SyncPlayIsInGroup)]
public async Task<ActionResult> SyncPlayUnpause()
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new UnpauseGroupRequest();
_syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
return NoContent();
@@ -229,7 +229,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.SyncPlayIsInGroup)]
public async Task<ActionResult> SyncPlayPause()
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new PauseGroupRequest();
_syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
return NoContent();
@@ -245,7 +245,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.SyncPlayIsInGroup)]
public async Task<ActionResult> SyncPlayStop()
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new StopGroupRequest();
_syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
return NoContent();
@@ -263,7 +263,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> SyncPlaySeek(
[FromBody, Required] SeekRequestDto requestData)
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new SeekGroupRequest(requestData.PositionTicks);
_syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
return NoContent();
@@ -281,7 +281,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> SyncPlayBuffering(
[FromBody, Required] BufferRequestDto requestData)
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new BufferGroupRequest(
requestData.When,
requestData.PositionTicks,
@@ -303,7 +303,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> SyncPlayReady(
[FromBody, Required] ReadyRequestDto requestData)
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new ReadyGroupRequest(
requestData.When,
requestData.PositionTicks,
@@ -325,7 +325,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> SyncPlaySetIgnoreWait(
[FromBody, Required] IgnoreWaitRequestDto requestData)
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new IgnoreWaitGroupRequest(requestData.IgnoreWait);
_syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
return NoContent();
@@ -343,7 +343,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> SyncPlayNextItem(
[FromBody, Required] NextItemRequestDto requestData)
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new NextItemGroupRequest(requestData.PlaylistItemId);
_syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
return NoContent();
@@ -361,7 +361,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> SyncPlayPreviousItem(
[FromBody, Required] PreviousItemRequestDto requestData)
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new PreviousItemGroupRequest(requestData.PlaylistItemId);
_syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
return NoContent();
@@ -379,7 +379,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> SyncPlaySetRepeatMode(
[FromBody, Required] SetRepeatModeRequestDto requestData)
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new SetRepeatModeGroupRequest(requestData.Mode);
_syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
return NoContent();
@@ -397,7 +397,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> SyncPlaySetShuffleMode(
[FromBody, Required] SetShuffleModeRequestDto requestData)
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new SetShuffleModeGroupRequest(requestData.Mode);
_syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
return NoContent();
@@ -414,7 +414,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> SyncPlayPing(
[FromBody, Required] PingRequestDto requestData)
{
- var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false);
+ var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var syncPlayRequest = new PingGroupRequest(requestData.Ping);
_syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
return NoContent();
diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs
index 5cb7468b2..b296d1c96 100644
--- a/Jellyfin.Api/Controllers/TrailersController.cs
+++ b/Jellyfin.Api/Controllers/TrailersController.cs
@@ -1,4 +1,5 @@
using System;
+using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
@@ -31,7 +32,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Finds movies and trailers similar to a given trailer.
/// </summary>
- /// <param name="userId">The user id.</param>
+ /// <param name="userId">The user id supplied as query parameter; this is required when not using an API key.</param>
/// <param name="maxOfficialRating">Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).</param>
/// <param name="hasThemeSong">Optional filter by items with theme songs.</param>
/// <param name="hasThemeVideo">Optional filter by items with theme videos.</param>
@@ -57,6 +58,11 @@ namespace Jellyfin.Api.Controllers
/// <param name="hasImdbId">Optional filter by items that have an imdb id or not.</param>
/// <param name="hasTmdbId">Optional filter by items that have a tmdb id or not.</param>
/// <param name="hasTvdbId">Optional filter by items that have a tvdb id or not.</param>
+ /// <param name="isMovie">Optional filter for live tv movies.</param>
+ /// <param name="isSeries">Optional filter for live tv series.</param>
+ /// <param name="isNews">Optional filter for live tv news.</param>
+ /// <param name="isKids">Optional filter for live tv kids.</param>
+ /// <param name="isSports">Optional filter for live tv sports.</param>
/// <param name="excludeItemIds">Optional. If specified, results will be filtered by excluding item ids. This allows multiple, comma delimited.</param>
/// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param>
@@ -114,14 +120,14 @@ namespace Jellyfin.Api.Controllers
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetTrailers(
- [FromQuery] Guid userId,
+ [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] Guid? adjacentTo,
[FromQuery] int? parentIndexNumber,
[FromQuery] bool? hasParentalRating,
[FromQuery] bool? isHd,
@@ -140,6 +146,11 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? hasImdbId,
[FromQuery] bool? hasTmdbId,
[FromQuery] bool? hasTvdbId,
+ [FromQuery] bool? isMovie,
+ [FromQuery] bool? isSeries,
+ [FromQuery] bool? isNews,
+ [FromQuery] bool? isKids,
+ [FromQuery] bool? isSports,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeItemIds,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
@@ -224,6 +235,11 @@ namespace Jellyfin.Api.Controllers
hasImdbId,
hasTmdbId,
hasTvdbId,
+ isMovie,
+ isSeries,
+ isNews,
+ isKids,
+ isSports,
excludeItemIds,
startIndex,
limit,
diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs
index 179a53fd5..ea13ceb91 100644
--- a/Jellyfin.Api/Controllers/TvShowsController.cs
+++ b/Jellyfin.Api/Controllers/TvShowsController.cs
@@ -77,7 +77,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery] string? seriesId,
+ [FromQuery] Guid? seriesId,
[FromQuery] Guid? parentId,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
@@ -89,7 +89,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool enableRewatching = false)
{
var options = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
var result = _tvSeriesManager.GetNextUp(
@@ -154,7 +154,7 @@ namespace Jellyfin.Api.Controllers
var parentIdGuid = parentId ?? Guid.Empty;
var options = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
@@ -206,7 +206,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? season,
[FromQuery] Guid? seasonId,
[FromQuery] bool? isMissing,
- [FromQuery] string? adjacentTo,
+ [FromQuery] Guid? adjacentTo,
[FromQuery] Guid? startItemId,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
@@ -223,7 +223,7 @@ namespace Jellyfin.Api.Controllers
List<BaseItem> episodes;
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
if (seasonId.HasValue) // Season id was supplied. Get episodes by season id.
@@ -278,9 +278,9 @@ namespace Jellyfin.Api.Controllers
}
// This must be the last filter
- if (!string.IsNullOrEmpty(adjacentTo))
+ if (adjacentTo.HasValue && !adjacentTo.Value.Equals(default))
{
- episodes = UserViewBuilder.FilterForAdjacency(episodes, adjacentTo).ToList();
+ episodes = UserViewBuilder.FilterForAdjacency(episodes, adjacentTo.Value).ToList();
}
if (string.Equals(sortBy, ItemSortBy.Random, StringComparison.OrdinalIgnoreCase))
@@ -326,7 +326,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool? isSpecialSeason,
[FromQuery] bool? isMissing,
- [FromQuery] string? adjacentTo,
+ [FromQuery] Guid? adjacentTo,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
@@ -349,7 +349,7 @@ namespace Jellyfin.Api.Controllers
});
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
var returnItems = _dtoService.GetBaseItemDtos(seasons, dtoOptions, user);
diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs
index 6fcafd426..01e13b4fe 100644
--- a/Jellyfin.Api/Controllers/UniversalAudioController.cs
+++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs
@@ -6,17 +6,15 @@ using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
+using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Session;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -30,8 +28,6 @@ namespace Jellyfin.Api.Controllers
[Route("")]
public class UniversalAudioController : BaseJellyfinApiController
{
- private readonly IAuthorizationContext _authorizationContext;
- private readonly IDeviceManager _deviceManager;
private readonly ILibraryManager _libraryManager;
private readonly ILogger<UniversalAudioController> _logger;
private readonly MediaInfoHelper _mediaInfoHelper;
@@ -41,24 +37,18 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Initializes a new instance of the <see cref="UniversalAudioController"/> class.
/// </summary>
- /// <param name="authorizationContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
- /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{UniversalAudioController}"/> interface.</param>
/// <param name="mediaInfoHelper">Instance of <see cref="MediaInfoHelper"/>.</param>
/// <param name="audioHelper">Instance of <see cref="AudioHelper"/>.</param>
/// <param name="dynamicHlsHelper">Instance of <see cref="DynamicHlsHelper"/>.</param>
public UniversalAudioController(
- IAuthorizationContext authorizationContext,
- IDeviceManager deviceManager,
ILibraryManager libraryManager,
ILogger<UniversalAudioController> logger,
MediaInfoHelper mediaInfoHelper,
AudioHelper audioHelper,
DynamicHlsHelper dynamicHlsHelper)
{
- _authorizationContext = authorizationContext;
- _deviceManager = deviceManager;
_libraryManager = libraryManager;
_logger = logger;
_mediaInfoHelper = mediaInfoHelper;
@@ -117,76 +107,57 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool enableRedirection = true)
{
var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels);
- (await _authorizationContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).DeviceId = deviceId;
- var authInfo = await _authorizationContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
-
- _logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", deviceProfile);
-
- if (deviceProfile == null)
+ if (!userId.HasValue || userId.Value.Equals(Guid.Empty))
{
- var clientCapabilities = _deviceManager.GetCapabilities(authInfo.DeviceId);
- if (clientCapabilities != null)
- {
- deviceProfile = clientCapabilities.DeviceProfile;
- }
+ userId = User.GetUserId();
}
+ _logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", deviceProfile);
+
var info = await _mediaInfoHelper.GetPlaybackInfo(
itemId,
userId,
mediaSourceId)
.ConfigureAwait(false);
- if (deviceProfile != null)
- {
- // set device specific data
- var item = _libraryManager.GetItemById(itemId);
-
- foreach (var sourceInfo in info.MediaSources)
- {
- _mediaInfoHelper.SetDeviceSpecificData(
- item,
- sourceInfo,
- deviceProfile,
- authInfo,
- maxStreamingBitrate ?? deviceProfile.MaxStreamingBitrate,
- startTimeTicks ?? 0,
- mediaSourceId ?? string.Empty,
- null,
- null,
- maxAudioChannels,
- info.PlaySessionId!,
- userId ?? Guid.Empty,
- true,
- true,
- true,
- true,
- true,
- Request.HttpContext.GetNormalizedRemoteIp());
- }
+ // set device specific data
+ var item = _libraryManager.GetItemById(itemId);
- _mediaInfoHelper.SortMediaSources(info, maxStreamingBitrate);
+ foreach (var sourceInfo in info.MediaSources)
+ {
+ _mediaInfoHelper.SetDeviceSpecificData(
+ item,
+ sourceInfo,
+ deviceProfile,
+ User,
+ maxStreamingBitrate ?? deviceProfile.MaxStreamingBitrate,
+ startTimeTicks ?? 0,
+ mediaSourceId ?? string.Empty,
+ null,
+ null,
+ maxAudioChannels,
+ info.PlaySessionId!,
+ userId ?? Guid.Empty,
+ true,
+ true,
+ true,
+ true,
+ true,
+ Request.HttpContext.GetNormalizedRemoteIp());
}
- if (info.MediaSources != null)
+ _mediaInfoHelper.SortMediaSources(info, maxStreamingBitrate);
+
+ foreach (var source in info.MediaSources)
{
- foreach (var source in info.MediaSources)
- {
- _mediaInfoHelper.NormalizeMediaSourceContainer(source, deviceProfile!, DlnaProfileType.Video);
- }
+ _mediaInfoHelper.NormalizeMediaSourceContainer(source, deviceProfile, DlnaProfileType.Video);
}
- var mediaSource = info.MediaSources![0];
- if (mediaSource.SupportsDirectPlay && mediaSource.Protocol == MediaProtocol.Http)
+ var mediaSource = info.MediaSources[0];
+ if (mediaSource.SupportsDirectPlay && mediaSource.Protocol == MediaProtocol.Http && enableRedirection && mediaSource.IsRemote && enableRemoteMedia.HasValue && enableRemoteMedia.Value)
{
- if (enableRedirection)
- {
- if (mediaSource.IsRemote && enableRemoteMedia.HasValue && enableRemoteMedia.Value)
- {
- return Redirect(mediaSource.Path);
- }
- }
+ return Redirect(mediaSource.Path);
}
var isStatic = mediaSource.SupportsDirectStream;
@@ -249,7 +220,7 @@ namespace Jellyfin.Api.Controllers
BreakOnNonKeyFrames = breakOnNonKeyFrames,
AudioSampleRate = maxAudioSampleRate,
MaxAudioChannels = maxAudioChannels,
- AudioBitRate = isStatic ? (int?)null : (audioBitRate ?? maxStreamingBitrate),
+ AudioBitRate = isStatic ? null : (audioBitRate ?? maxStreamingBitrate),
MaxAudioBitDepth = maxAudioBitDepth,
AudioChannels = maxAudioChannels,
CopyTimestamps = true,
diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs
index 6d15d9185..ff653fe6b 100644
--- a/Jellyfin.Api/Controllers/UserController.cs
+++ b/Jellyfin.Api/Controllers/UserController.cs
@@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
+using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.UserDtos;
using Jellyfin.Data.Enums;
@@ -82,11 +83,11 @@ namespace Jellyfin.Api.Controllers
[HttpGet]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
- public async Task<ActionResult<IEnumerable<UserDto>>> GetUsers(
+ public ActionResult<IEnumerable<UserDto>> GetUsers(
[FromQuery] bool? isHidden,
[FromQuery] bool? isDisabled)
{
- var users = await Get(isHidden, isDisabled, false, false).ConfigureAwait(false);
+ var users = Get(isHidden, isDisabled, false, false);
return Ok(users);
}
@@ -97,15 +98,15 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="IEnumerable{UserDto}"/> containing the public users.</returns>
[HttpGet("Public")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public async Task<ActionResult<IEnumerable<UserDto>>> GetPublicUsers()
+ public ActionResult<IEnumerable<UserDto>> GetPublicUsers()
{
// If the startup wizard hasn't been completed then just return all users
if (!_config.Configuration.IsStartupWizardCompleted)
{
- return Ok(await Get(false, false, false, false).ConfigureAwait(false));
+ return Ok(Get(false, false, false, false));
}
- return Ok(await Get(false, false, true, true).ConfigureAwait(false));
+ return Ok(Get(false, false, true, true));
}
/// <summary>
@@ -264,7 +265,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] Guid userId,
[FromBody, Required] UpdateUserPassword request)
{
- if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false))
+ if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, userId, true))
{
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the password.");
}
@@ -282,21 +283,24 @@ namespace Jellyfin.Api.Controllers
}
else
{
- var success = await _userManager.AuthenticateUser(
- user.Username,
- request.CurrentPw,
- request.CurrentPw,
- HttpContext.GetNormalizedRemoteIp().ToString(),
- false).ConfigureAwait(false);
-
- if (success == null)
+ if (!User.IsInRole(UserRoles.Administrator))
{
- return StatusCode(StatusCodes.Status403Forbidden, "Invalid user or password entered.");
+ var success = await _userManager.AuthenticateUser(
+ user.Username,
+ request.CurrentPw,
+ request.CurrentPw,
+ HttpContext.GetNormalizedRemoteIp().ToString(),
+ false).ConfigureAwait(false);
+
+ if (success == null)
+ {
+ return StatusCode(StatusCodes.Status403Forbidden, "Invalid user or password entered.");
+ }
}
await _userManager.ChangePassword(user, request.NewPw).ConfigureAwait(false);
- var currentToken = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).Token;
+ var currentToken = User.GetToken();
await _sessionManager.RevokeUserTokens(user.Id, currentToken).ConfigureAwait(false);
}
@@ -322,7 +326,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] Guid userId,
[FromBody, Required] UpdateUserEasyPassword request)
{
- if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false))
+ if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, userId, true))
{
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the easy password.");
}
@@ -364,7 +368,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] Guid userId,
[FromBody, Required] UserDto updateUser)
{
- if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, false).ConfigureAwait(false))
+ if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, userId, true))
{
return StatusCode(StatusCodes.Status403Forbidden, "User update not allowed.");
}
@@ -424,7 +428,7 @@ namespace Jellyfin.Api.Controllers
return StatusCode(StatusCodes.Status403Forbidden, "There must be at least one enabled user in the system.");
}
- var currentToken = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).Token;
+ var currentToken = User.GetToken();
await _sessionManager.RevokeUserTokens(user.Id, currentToken).ConfigureAwait(false);
}
@@ -449,7 +453,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] Guid userId,
[FromBody, Required] UserConfiguration userConfig)
{
- if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, false).ConfigureAwait(false))
+ if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, userId, true))
{
return StatusCode(StatusCodes.Status403Forbidden, "User configuration update not allowed");
}
@@ -499,7 +503,7 @@ namespace Jellyfin.Api.Controllers
if (isLocal)
{
- _logger.LogWarning("Password reset proccess initiated from outside the local network with IP: {IP}", ip);
+ _logger.LogWarning("Password reset process initiated from outside the local network with IP: {IP}", ip);
}
var result = await _userManager.StartForgotPasswordProcess(forgotPasswordRequest.EnteredUsername, isLocal).ConfigureAwait(false);
@@ -533,13 +537,13 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public ActionResult<UserDto> GetCurrentUser()
{
- var userId = ClaimHelpers.GetUserId(Request.HttpContext.User);
- if (userId is null)
+ var userId = User.GetUserId();
+ if (userId.Equals(default))
{
return BadRequest();
}
- var user = _userManager.GetUserById(userId.Value);
+ var user = _userManager.GetUserById(userId);
if (user == null)
{
return BadRequest();
@@ -548,7 +552,7 @@ namespace Jellyfin.Api.Controllers
return _userManager.GetUserDto(user);
}
- private async Task<IEnumerable<UserDto>> Get(bool? isHidden, bool? isDisabled, bool filterByDevice, bool filterByNetwork)
+ private IEnumerable<UserDto> Get(bool? isHidden, bool? isDisabled, bool filterByDevice, bool filterByNetwork)
{
var users = _userManager.Users;
@@ -564,7 +568,7 @@ namespace Jellyfin.Api.Controllers
if (filterByDevice)
{
- var deviceId = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).DeviceId;
+ var deviceId = User.GetDeviceId();
if (!string.IsNullOrWhiteSpace(deviceId))
{
diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs
index e45f9b58c..ee8a17b62 100644
--- a/Jellyfin.Api/Controllers/UserLibraryController.cs
+++ b/Jellyfin.Api/Controllers/UserLibraryController.cs
@@ -8,7 +8,6 @@ using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
-using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@@ -82,7 +81,7 @@ namespace Jellyfin.Api.Controllers
await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false);
- var dtoOptions = new DtoOptions().AddClientFields(Request);
+ var dtoOptions = new DtoOptions().AddClientFields(User);
return _dtoService.GetBaseItemDto(item, dtoOptions, user);
}
@@ -99,7 +98,7 @@ namespace Jellyfin.Api.Controllers
{
var user = _userManager.GetUserById(userId);
var item = _libraryManager.GetUserRootFolder();
- var dtoOptions = new DtoOptions().AddClientFields(Request);
+ var dtoOptions = new DtoOptions().AddClientFields(User);
return _dtoService.GetBaseItemDto(item, dtoOptions, user);
}
@@ -121,7 +120,7 @@ namespace Jellyfin.Api.Controllers
: _libraryManager.GetItemById(itemId);
var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false);
- var dtoOptions = new DtoOptions().AddClientFields(Request);
+ var dtoOptions = new DtoOptions().AddClientFields(User);
var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray();
return new QueryResult<BaseItemDto>(dtos);
@@ -201,7 +200,7 @@ namespace Jellyfin.Api.Controllers
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
- var dtoOptions = new DtoOptions().AddClientFields(Request);
+ var dtoOptions = new DtoOptions().AddClientFields(User);
if (item is IHasTrailers hasTrailers)
{
@@ -231,10 +230,11 @@ namespace Jellyfin.Api.Controllers
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
- var dtoOptions = new DtoOptions().AddClientFields(Request);
+ var dtoOptions = new DtoOptions().AddClientFields(User);
return Ok(item
- .GetExtras(BaseItem.DisplayExtraTypes)
+ .GetExtras()
+ .Where(i => i.ExtraType.HasValue && BaseItem.DisplayExtraTypes.Contains(i.ExtraType.Value))
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)));
}
@@ -280,7 +280,7 @@ namespace Jellyfin.Api.Controllers
}
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
var list = _userViewManager.GetLatestItems(
diff --git a/Jellyfin.Api/Controllers/UserViewsController.cs b/Jellyfin.Api/Controllers/UserViewsController.cs
index 5cc8c906f..85d154cac 100644
--- a/Jellyfin.Api/Controllers/UserViewsController.cs
+++ b/Jellyfin.Api/Controllers/UserViewsController.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
-using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.ModelBinders;
@@ -11,9 +10,7 @@ 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.Authorization;
@@ -32,7 +29,6 @@ namespace Jellyfin.Api.Controllers
private readonly IUserManager _userManager;
private readonly IUserViewManager _userViewManager;
private readonly IDtoService _dtoService;
- private readonly IAuthorizationContext _authContext;
private readonly ILibraryManager _libraryManager;
/// <summary>
@@ -41,19 +37,16 @@ namespace Jellyfin.Api.Controllers
/// <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;
}
@@ -92,7 +85,7 @@ namespace Jellyfin.Api.Controllers
var folders = _userViewManager.GetUserViews(query);
- var dtoOptions = new DtoOptions().AddClientFields(Request);
+ var dtoOptions = new DtoOptions().AddClientFields(User);
var fields = dtoOptions.Fields.ToList();
fields.Add(ItemFields.PrimaryImageAspectRatio);
@@ -138,7 +131,8 @@ namespace Jellyfin.Api.Controllers
Name = i.Name,
Id = i.Id.ToString("N", CultureInfo.InvariantCulture)
})
- .OrderBy(i => i.Name));
+ .OrderBy(i => i.Name)
+ .AsEnumerable());
}
}
}
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index 62c05331e..bf08ad376 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -43,7 +43,6 @@ namespace Jellyfin.Api.Controllers
private readonly IUserManager _userManager;
private readonly IDtoService _dtoService;
private readonly IDlnaManager _dlnaManager;
- private readonly IAuthorizationContext _authContext;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IMediaEncoder _mediaEncoder;
@@ -61,7 +60,6 @@ namespace Jellyfin.Api.Controllers
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
/// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
- /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
@@ -74,7 +72,6 @@ namespace Jellyfin.Api.Controllers
IUserManager userManager,
IDtoService dtoService,
IDlnaManager dlnaManager,
- IAuthorizationContext authContext,
IMediaSourceManager mediaSourceManager,
IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder,
@@ -87,7 +84,6 @@ namespace Jellyfin.Api.Controllers
_userManager = userManager;
_dtoService = dtoService;
_dlnaManager = dlnaManager;
- _authContext = authContext;
_mediaSourceManager = mediaSourceManager;
_serverConfigurationManager = serverConfigurationManager;
_mediaEncoder = mediaEncoder;
@@ -120,7 +116,7 @@ namespace Jellyfin.Api.Controllers
: _libraryManager.GetItemById(itemId);
var dtoOptions = new DtoOptions();
- dtoOptions = dtoOptions.AddClientFields(Request);
+ dtoOptions = dtoOptions.AddClientFields(User);
BaseItemDto[] items;
if (item is Video video)
@@ -427,10 +423,9 @@ namespace Jellyfin.Api.Controllers
StreamOptions = streamOptions
};
- using var state = await StreamingHelpers.GetStreamingState(
+ var state = await StreamingHelpers.GetStreamingState(
streamingRequest,
- Request,
- _authContext,
+ HttpContext,
_mediaSourceManager,
_userManager,
_libraryManager,
diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs
index 7c02e2550..b732bdff3 100644
--- a/Jellyfin.Api/Controllers/YearsController.cs
+++ b/Jellyfin.Api/Controllers/YearsController.cs
@@ -87,7 +87,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? enableImages = true)
{
var dtoOptions = new DtoOptions { Fields = fields }
- .AddClientFields(Request)
+ .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
User? user = userId is null || userId.Value.Equals(default)
@@ -179,7 +179,7 @@ namespace Jellyfin.Api.Controllers
}
var dtoOptions = new DtoOptions()
- .AddClientFields(Request);
+ .AddClientFields(User);
if (userId.HasValue && !userId.Value.Equals(default))
{
diff --git a/Jellyfin.Api/Extensions/ClaimsPrincipalExtensions.cs b/Jellyfin.Api/Extensions/ClaimsPrincipalExtensions.cs
new file mode 100644
index 000000000..6b3e78d4d
--- /dev/null
+++ b/Jellyfin.Api/Extensions/ClaimsPrincipalExtensions.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Linq;
+using System.Security.Claims;
+using Jellyfin.Api.Constants;
+
+namespace Jellyfin.Api.Extensions;
+
+/// <summary>
+/// Extensions for <see cref="ClaimsPrincipal"/>.
+/// </summary>
+public static class ClaimsPrincipalExtensions
+{
+ /// <summary>
+ /// Get user id from claims.
+ /// </summary>
+ /// <param name="user">Current claims principal.</param>
+ /// <returns>User id.</returns>
+ public static Guid GetUserId(this ClaimsPrincipal user)
+ {
+ var value = GetClaimValue(user, InternalClaimTypes.UserId);
+ return string.IsNullOrEmpty(value)
+ ? default
+ : Guid.Parse(value);
+ }
+
+ /// <summary>
+ /// Get device id from claims.
+ /// </summary>
+ /// <param name="user">Current claims principal.</param>
+ /// <returns>Device id.</returns>
+ public static string? GetDeviceId(this ClaimsPrincipal user)
+ => GetClaimValue(user, InternalClaimTypes.DeviceId);
+
+ /// <summary>
+ /// Get device from claims.
+ /// </summary>
+ /// <param name="user">Current claims principal.</param>
+ /// <returns>Device.</returns>
+ public static string? GetDevice(this ClaimsPrincipal user)
+ => GetClaimValue(user, InternalClaimTypes.Device);
+
+ /// <summary>
+ /// Get client from claims.
+ /// </summary>
+ /// <param name="user">Current claims principal.</param>
+ /// <returns>Client.</returns>
+ public static string? GetClient(this ClaimsPrincipal user)
+ => GetClaimValue(user, InternalClaimTypes.Client);
+
+ /// <summary>
+ /// Get version from claims.
+ /// </summary>
+ /// <param name="user">Current claims principal.</param>
+ /// <returns>Version.</returns>
+ public static string? GetVersion(this ClaimsPrincipal user)
+ => GetClaimValue(user, InternalClaimTypes.Version);
+
+ /// <summary>
+ /// Get token from claims.
+ /// </summary>
+ /// <param name="user">Current claims principal.</param>
+ /// <returns>Token.</returns>
+ public static string? GetToken(this ClaimsPrincipal user)
+ => GetClaimValue(user, InternalClaimTypes.Token);
+
+ /// <summary>
+ /// Gets a flag specifying whether the request is using an api key.
+ /// </summary>
+ /// <param name="user">Current claims principal.</param>
+ /// <returns>The flag specifying whether the request is using an api key.</returns>
+ public static bool GetIsApiKey(this ClaimsPrincipal user)
+ {
+ var claimValue = GetClaimValue(user, InternalClaimTypes.IsApiKey);
+ return !string.IsNullOrEmpty(claimValue)
+ && bool.TryParse(claimValue, out var parsedClaimValue)
+ && parsedClaimValue;
+ }
+
+ private static string? GetClaimValue(in ClaimsPrincipal user, string name)
+ => user.Claims.FirstOrDefault(claim => claim.Type.Equals(name, StringComparison.OrdinalIgnoreCase))?.Value;
+}
diff --git a/Jellyfin.Api/Extensions/DtoExtensions.cs b/Jellyfin.Api/Extensions/DtoExtensions.cs
index 5e338b67d..9e784f7c4 100644
--- a/Jellyfin.Api/Extensions/DtoExtensions.cs
+++ b/Jellyfin.Api/Extensions/DtoExtensions.cs
@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
-using Jellyfin.Api.Helpers;
+using System.Security.Claims;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Model.Entities;
@@ -22,14 +22,14 @@ namespace Jellyfin.Api.Extensions
/// Legacy order: 2.
/// </remarks>
/// <param name="dtoOptions">DtoOptions object.</param>
- /// <param name="request">Current request.</param>
+ /// <param name="user">Current claims principal.</param>
/// <returns>Modified DtoOptions object.</returns>
internal static DtoOptions AddClientFields(
- this DtoOptions dtoOptions, HttpRequest request)
+ this DtoOptions dtoOptions, ClaimsPrincipal user)
{
dtoOptions.Fields ??= Array.Empty<ItemFields>();
- string? client = ClaimHelpers.GetClient(request.HttpContext.User);
+ string? client = user.GetClient();
// No client in claim
if (string.IsNullOrEmpty(client))
diff --git a/Jellyfin.Api/Helpers/AudioHelper.cs b/Jellyfin.Api/Helpers/AudioHelper.cs
index 27497cd59..bc83ff48a 100644
--- a/Jellyfin.Api/Helpers/AudioHelper.cs
+++ b/Jellyfin.Api/Helpers/AudioHelper.cs
@@ -11,7 +11,6 @@ using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Net;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Net;
using Microsoft.AspNetCore.Http;
@@ -25,7 +24,6 @@ namespace Jellyfin.Api.Helpers
public class AudioHelper
{
private readonly IDlnaManager _dlnaManager;
- private readonly IAuthorizationContext _authContext;
private readonly IUserManager _userManager;
private readonly ILibraryManager _libraryManager;
private readonly IMediaSourceManager _mediaSourceManager;
@@ -41,7 +39,6 @@ namespace Jellyfin.Api.Helpers
/// Initializes a new instance of the <see cref="AudioHelper"/> class.
/// </summary>
/// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
- /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> 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="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
@@ -54,7 +51,6 @@ namespace Jellyfin.Api.Helpers
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
public AudioHelper(
IDlnaManager dlnaManager,
- IAuthorizationContext authContext,
IUserManager userManager,
ILibraryManager libraryManager,
IMediaSourceManager mediaSourceManager,
@@ -67,7 +63,6 @@ namespace Jellyfin.Api.Helpers
EncodingHelper encodingHelper)
{
_dlnaManager = dlnaManager;
- _authContext = authContext;
_userManager = userManager;
_libraryManager = libraryManager;
_mediaSourceManager = mediaSourceManager;
@@ -102,8 +97,7 @@ namespace Jellyfin.Api.Helpers
using var state = await StreamingHelpers.GetStreamingState(
streamingRequest,
- _httpContextAccessor.HttpContext.Request,
- _authContext,
+ _httpContextAccessor.HttpContext,
_mediaSourceManager,
_userManager,
_libraryManager,
diff --git a/Jellyfin.Api/Helpers/ClaimHelpers.cs b/Jellyfin.Api/Helpers/ClaimHelpers.cs
deleted file mode 100644
index c1c2f93b4..000000000
--- a/Jellyfin.Api/Helpers/ClaimHelpers.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-using System;
-using System.Linq;
-using System.Security.Claims;
-using Jellyfin.Api.Constants;
-
-namespace Jellyfin.Api.Helpers
-{
- /// <summary>
- /// Claim Helpers.
- /// </summary>
- public static class ClaimHelpers
- {
- /// <summary>
- /// Get user id from claims.
- /// </summary>
- /// <param name="user">Current claims principal.</param>
- /// <returns>User id.</returns>
- public static Guid? GetUserId(in ClaimsPrincipal user)
- {
- var value = GetClaimValue(user, InternalClaimTypes.UserId);
- return string.IsNullOrEmpty(value)
- ? null
- : Guid.Parse(value);
- }
-
- /// <summary>
- /// Get device id from claims.
- /// </summary>
- /// <param name="user">Current claims principal.</param>
- /// <returns>Device id.</returns>
- public static string? GetDeviceId(in ClaimsPrincipal user)
- => GetClaimValue(user, InternalClaimTypes.DeviceId);
-
- /// <summary>
- /// Get device from claims.
- /// </summary>
- /// <param name="user">Current claims principal.</param>
- /// <returns>Device.</returns>
- public static string? GetDevice(in ClaimsPrincipal user)
- => GetClaimValue(user, InternalClaimTypes.Device);
-
- /// <summary>
- /// Get client from claims.
- /// </summary>
- /// <param name="user">Current claims principal.</param>
- /// <returns>Client.</returns>
- public static string? GetClient(in ClaimsPrincipal user)
- => GetClaimValue(user, InternalClaimTypes.Client);
-
- /// <summary>
- /// Get version from claims.
- /// </summary>
- /// <param name="user">Current claims principal.</param>
- /// <returns>Version.</returns>
- public static string? GetVersion(in ClaimsPrincipal user)
- => GetClaimValue(user, InternalClaimTypes.Version);
-
- /// <summary>
- /// Get token from claims.
- /// </summary>
- /// <param name="user">Current claims principal.</param>
- /// <returns>Token.</returns>
- public static string? GetToken(in ClaimsPrincipal user)
- => GetClaimValue(user, InternalClaimTypes.Token);
-
- /// <summary>
- /// Gets a flag specifying whether the request is using an api key.
- /// </summary>
- /// <param name="user">Current claims principal.</param>
- /// <returns>The flag specifying whether the request is using an api key.</returns>
- public static bool GetIsApiKey(in ClaimsPrincipal user)
- {
- var claimValue = GetClaimValue(user, InternalClaimTypes.IsApiKey);
- return !string.IsNullOrEmpty(claimValue)
- && bool.TryParse(claimValue, out var parsedClaimValue)
- && parsedClaimValue;
- }
-
- private static string? GetClaimValue(in ClaimsPrincipal user, string name)
- {
- return user?.Identities
- .SelectMany(c => c.Claims)
- .Where(claim => claim.Type.Equals(name, StringComparison.OrdinalIgnoreCase))
- .Select(claim => claim.Value)
- .FirstOrDefault();
- }
- }
-}
diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
index 02af2e435..fa392e567 100644
--- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
+++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
@@ -7,6 +7,7 @@ using System.Security.Claims;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Api.Extensions;
using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
@@ -15,7 +16,6 @@ using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Net;
@@ -34,7 +34,6 @@ namespace Jellyfin.Api.Helpers
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
private readonly IDlnaManager _dlnaManager;
- private readonly IAuthorizationContext _authContext;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IMediaEncoder _mediaEncoder;
@@ -51,7 +50,6 @@ namespace Jellyfin.Api.Helpers
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
- /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
@@ -65,7 +63,6 @@ namespace Jellyfin.Api.Helpers
ILibraryManager libraryManager,
IUserManager userManager,
IDlnaManager dlnaManager,
- IAuthorizationContext authContext,
IMediaSourceManager mediaSourceManager,
IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder,
@@ -79,7 +76,6 @@ namespace Jellyfin.Api.Helpers
_libraryManager = libraryManager;
_userManager = userManager;
_dlnaManager = dlnaManager;
- _authContext = authContext;
_mediaSourceManager = mediaSourceManager;
_serverConfigurationManager = serverConfigurationManager;
_mediaEncoder = mediaEncoder;
@@ -128,8 +124,7 @@ namespace Jellyfin.Api.Helpers
using var state = await StreamingHelpers.GetStreamingState(
streamingRequest,
- _httpContextAccessor.HttpContext.Request,
- _authContext,
+ _httpContextAccessor.HttpContext,
_mediaSourceManager,
_userManager,
_libraryManager,
@@ -216,7 +211,7 @@ namespace Jellyfin.Api.Helpers
var sdrVideoUrl = ReplaceProfile(playlistUrl, "hevc", string.Join(',', requestedVideoProfiles), "main");
sdrVideoUrl += "&AllowVideoStreamCopy=false";
- var sdrOutputVideoBitrate = _encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec) ?? 0;
+ var sdrOutputVideoBitrate = _encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);
var sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream) ?? 0;
var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate;
@@ -483,7 +478,7 @@ namespace Jellyfin.Api.Helpers
state.Request.MediaSourceId,
stream.Index.ToString(CultureInfo.InvariantCulture),
30.ToString(CultureInfo.InvariantCulture),
- ClaimHelpers.GetToken(user));
+ user.GetToken());
var line = string.Format(
CultureInfo.InvariantCulture,
diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs
index 31b979836..4441ae023 100644
--- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs
+++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs
@@ -2,9 +2,11 @@
using System.Globalization;
using System.Linq;
using System.Net;
+using System.Security.Claims;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Api.Extensions;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions;
@@ -15,7 +17,6 @@ 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;
@@ -39,7 +40,6 @@ namespace Jellyfin.Api.Helpers
private readonly ILogger<MediaInfoHelper> _logger;
private readonly INetworkManager _networkManager;
private readonly IDeviceManager _deviceManager;
- private readonly IAuthorizationContext _authContext;
/// <summary>
/// Initializes a new instance of the <see cref="MediaInfoHelper"/> class.
@@ -52,7 +52,6 @@ namespace Jellyfin.Api.Helpers
/// <param name="logger">Instance of the <see cref="ILogger{MediaInfoHelper}"/> interface.</param>
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
/// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
- /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
public MediaInfoHelper(
IUserManager userManager,
ILibraryManager libraryManager,
@@ -61,8 +60,7 @@ namespace Jellyfin.Api.Helpers
IServerConfigurationManager serverConfigurationManager,
ILogger<MediaInfoHelper> logger,
INetworkManager networkManager,
- IDeviceManager deviceManager,
- IAuthorizationContext authContext)
+ IDeviceManager deviceManager)
{
_userManager = userManager;
_libraryManager = libraryManager;
@@ -72,7 +70,6 @@ namespace Jellyfin.Api.Helpers
_logger = logger;
_networkManager = networkManager;
_deviceManager = deviceManager;
- _authContext = authContext;
}
/// <summary>
@@ -147,7 +144,7 @@ namespace Jellyfin.Api.Helpers
/// <param name="item">Item to set data for.</param>
/// <param name="mediaSource">Media source info.</param>
/// <param name="profile">Device profile.</param>
- /// <param name="auth">Authorization info.</param>
+ /// <param name="claimsPrincipal">Current claims principal.</param>
/// <param name="maxBitrate">Max bitrate.</param>
/// <param name="startTimeTicks">Start time ticks.</param>
/// <param name="mediaSourceId">Media source id.</param>
@@ -166,7 +163,7 @@ namespace Jellyfin.Api.Helpers
BaseItem item,
MediaSourceInfo mediaSource,
DeviceProfile profile,
- AuthorizationInfo auth,
+ ClaimsPrincipal claimsPrincipal,
int? maxBitrate,
long startTimeTicks,
string mediaSourceId,
@@ -188,7 +185,7 @@ namespace Jellyfin.Api.Helpers
{
MediaSources = new[] { mediaSource },
Context = EncodingContext.Streaming,
- DeviceId = auth.DeviceId,
+ DeviceId = claimsPrincipal.GetDeviceId(),
ItemId = item.Id,
Profile = profile,
MaxAudioChannels = maxAudioChannels,
@@ -256,9 +253,17 @@ namespace Jellyfin.Api.Helpers
streamInfo.StartPositionTicks = startTimeTicks;
mediaSource.SupportsDirectPlay = streamInfo.PlayMethod == PlayMethod.DirectPlay;
+
// Players do not handle this being set according to PlayMethod
- mediaSource.SupportsDirectStream = options.EnableDirectStream ? streamInfo.PlayMethod == PlayMethod.DirectPlay || streamInfo.PlayMethod == PlayMethod.DirectStream : streamInfo.PlayMethod == PlayMethod.DirectPlay;
- mediaSource.SupportsTranscoding = streamInfo.PlayMethod == PlayMethod.DirectStream || mediaSource.TranscodingContainer != null;
+ mediaSource.SupportsDirectStream =
+ options.EnableDirectStream
+ ? streamInfo.PlayMethod == PlayMethod.DirectPlay || streamInfo.PlayMethod == PlayMethod.DirectStream
+ : streamInfo.PlayMethod == PlayMethod.DirectPlay;
+
+ mediaSource.SupportsTranscoding =
+ streamInfo.PlayMethod == PlayMethod.DirectStream
+ || mediaSource.TranscodingContainer != null
+ || profile.TranscodingProfiles.Any(i => i.Type == streamInfo.MediaType && i.Context == options.Context);
if (item is Audio)
{
@@ -282,7 +287,7 @@ namespace Jellyfin.Api.Helpers
mediaSource.SupportsDirectPlay = false;
mediaSource.SupportsDirectStream = false;
- mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
+ mediaSource.TranscodingUrl = streamInfo.ToUrl("-", claimsPrincipal.GetToken()).TrimStart('-');
mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
mediaSource.TranscodingContainer = streamInfo.Container;
@@ -290,10 +295,10 @@ namespace Jellyfin.Api.Helpers
}
else
{
- if (mediaSource.SupportsTranscoding || mediaSource.SupportsDirectStream)
+ if (!mediaSource.SupportsDirectPlay && (mediaSource.SupportsTranscoding || mediaSource.SupportsDirectStream))
{
streamInfo.PlayMethod = PlayMethod.Transcode;
- mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
+ mediaSource.TranscodingUrl = streamInfo.ToUrl("-", claimsPrincipal.GetToken()).TrimStart('-');
if (!allowVideoStreamCopy)
{
@@ -308,7 +313,8 @@ namespace Jellyfin.Api.Helpers
}
// Do this after the above so that StartPositionTicks is set
- SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+ // The token must not be null
+ SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, claimsPrincipal.GetToken()!);
mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex;
}
@@ -376,19 +382,17 @@ namespace Jellyfin.Api.Helpers
/// <summary>
/// Open media source.
/// </summary>
- /// <param name="httpRequest">Http Request.</param>
+ /// <param name="httpContext">Http Context.</param>
/// <param name="request">Live stream request.</param>
/// <returns>A <see cref="Task"/> containing the <see cref="LiveStreamResponse"/>.</returns>
- public async Task<LiveStreamResponse> OpenMediaSource(HttpRequest httpRequest, LiveStreamRequest request)
+ public async Task<LiveStreamResponse> OpenMediaSource(HttpContext httpContext, LiveStreamRequest request)
{
- var authInfo = await _authContext.GetAuthorizationInfo(httpRequest).ConfigureAwait(false);
-
var result = await _mediaSourceManager.OpenLiveStream(request, CancellationToken.None).ConfigureAwait(false);
var profile = request.DeviceProfile;
if (profile == null)
{
- var clientCapabilities = _deviceManager.GetCapabilities(authInfo.DeviceId);
+ var clientCapabilities = _deviceManager.GetCapabilities(httpContext.User.GetDeviceId());
if (clientCapabilities != null)
{
profile = clientCapabilities.DeviceProfile;
@@ -403,7 +407,7 @@ namespace Jellyfin.Api.Helpers
item,
result.MediaSource,
profile,
- authInfo,
+ httpContext.User,
request.MaxStreamingBitrate,
request.StartTimeTicks ?? 0,
result.MediaSource.Id,
@@ -417,7 +421,7 @@ namespace Jellyfin.Api.Helpers
true,
true,
true,
- httpRequest.HttpContext.GetNormalizedRemoteIp());
+ httpContext.GetNormalizedRemoteIp());
}
else
{
diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs
index 20427d7fa..8c5af013a 100644
--- a/Jellyfin.Api/Helpers/RequestHelpers.cs
+++ b/Jellyfin.Api/Helpers/RequestHelpers.cs
@@ -1,13 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Security.Claims;
using System.Threading.Tasks;
+using Jellyfin.Api.Constants;
+using Jellyfin.Api.Extensions;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Net;
+using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying;
@@ -55,37 +58,42 @@ namespace Jellyfin.Api.Helpers
/// <summary>
/// Checks if the user can update an entry.
/// </summary>
- /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
- /// <param name="requestContext">The <see cref="HttpRequest"/>.</param>
+ /// <param name="userManager">An instance of the <see cref="IUserManager"/> interface.</param>
+ /// <param name="claimsPrincipal">The <see cref="ClaimsPrincipal"/> for the current request.</param>
/// <param name="userId">The user id.</param>
/// <param name="restrictUserPreferences">Whether to restrict the user preferences.</param>
/// <returns>A <see cref="bool"/> whether the user can update the entry.</returns>
- internal static async Task<bool> AssertCanUpdateUser(IAuthorizationContext authContext, HttpRequest requestContext, Guid userId, bool restrictUserPreferences)
+ internal static bool AssertCanUpdateUser(IUserManager userManager, ClaimsPrincipal claimsPrincipal, Guid userId, bool restrictUserPreferences)
{
- var auth = await authContext.GetAuthorizationInfo(requestContext).ConfigureAwait(false);
-
- var authenticatedUser = auth.User;
+ var authenticatedUserId = claimsPrincipal.GetUserId();
+ var isAdministrator = claimsPrincipal.IsInRole(UserRoles.Administrator);
// If they're going to update the record of another user, they must be an administrator
- if ((!userId.Equals(auth.UserId) && !authenticatedUser.HasPermission(PermissionKind.IsAdministrator))
- || (restrictUserPreferences && !authenticatedUser.EnableUserPreferenceAccess))
+ if (!userId.Equals(authenticatedUserId) && !isAdministrator)
{
return false;
}
- return true;
+ // TODO the EnableUserPreferenceAccess policy does not seem to be used elsewhere
+ if (!restrictUserPreferences || isAdministrator)
+ {
+ return true;
+ }
+
+ var user = userManager.GetUserById(userId);
+ return user.EnableUserPreferenceAccess;
}
- internal static async Task<SessionInfo> GetSession(ISessionManager sessionManager, IAuthorizationContext authContext, HttpRequest request)
+ internal static async Task<SessionInfo> GetSession(ISessionManager sessionManager, IUserManager userManager, HttpContext httpContext)
{
- var authorization = await authContext.GetAuthorizationInfo(request).ConfigureAwait(false);
- var user = authorization.User;
+ var userId = httpContext.User.GetUserId();
+ var user = userManager.GetUserById(userId);
var session = await sessionManager.LogSessionActivity(
- authorization.Client,
- authorization.Version,
- authorization.DeviceId,
- authorization.Device,
- request.HttpContext.GetNormalizedRemoteIp().ToString(),
+ httpContext.User.GetClient(),
+ httpContext.User.GetVersion(),
+ httpContext.User.GetDeviceId(),
+ httpContext.User.GetDevice(),
+ httpContext.GetNormalizedRemoteIp().ToString(),
user).ConfigureAwait(false);
if (session == null)
@@ -96,9 +104,9 @@ namespace Jellyfin.Api.Helpers
return session;
}
- internal static async Task<string> GetSessionId(ISessionManager sessionManager, IAuthorizationContext authContext, HttpRequest request)
+ internal static async Task<string> GetSessionId(ISessionManager sessionManager, IUserManager userManager, HttpContext httpContext)
{
- var session = await GetSession(sessionManager, authContext, request).ConfigureAwait(false);
+ var session = await GetSession(sessionManager, userManager, httpContext).ConfigureAwait(false);
return session.Id;
}
diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs
index 34dab75b8..370573773 100644
--- a/Jellyfin.Api/Helpers/StreamingHelpers.cs
+++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs
@@ -5,6 +5,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Api.Extensions;
using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
@@ -14,7 +15,6 @@ using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@@ -33,8 +33,7 @@ namespace Jellyfin.Api.Helpers
/// Gets the current streaming state.
/// </summary>
/// <param name="streamingRequest">The <see cref="StreamingRequestDto"/>.</param>
- /// <param name="httpRequest">The <see cref="HttpRequest"/>.</param>
- /// <param name="authorizationContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
+ /// <param name="httpContext">The <see cref="HttpContext"/>.</param>
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
@@ -49,8 +48,7 @@ namespace Jellyfin.Api.Helpers
/// <returns>A <see cref="Task"/> containing the current <see cref="StreamState"/>.</returns>
public static async Task<StreamState> GetStreamingState(
StreamingRequestDto streamingRequest,
- HttpRequest httpRequest,
- IAuthorizationContext authorizationContext,
+ HttpContext httpContext,
IMediaSourceManager mediaSourceManager,
IUserManager userManager,
ILibraryManager libraryManager,
@@ -63,6 +61,7 @@ namespace Jellyfin.Api.Helpers
TranscodingJobType transcodingJobType,
CancellationToken cancellationToken)
{
+ var httpRequest = httpContext.Request;
// Parse the DLNA time seek header
if (!streamingRequest.StartTimeTicks.HasValue)
{
@@ -101,10 +100,10 @@ namespace Jellyfin.Api.Helpers
EnableDlnaHeaders = enableDlnaHeaders
};
- var auth = await authorizationContext.GetAuthorizationInfo(httpRequest).ConfigureAwait(false);
- if (!auth.UserId.Equals(default))
+ var userId = httpContext.User.GetUserId();
+ if (!userId.Equals(default))
{
- state.User = userManager.GetUserById(auth.UserId);
+ state.User = userManager.GetUserById(userId);
}
if (state.IsVideoRequest && !string.IsNullOrWhiteSpace(state.Request.VideoCodec))
@@ -179,7 +178,7 @@ namespace Jellyfin.Api.Helpers
{
containerInternal = streamingRequest.Static ?
StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, null, DlnaProfileType.Audio)
- : GetOutputFileExtension(state);
+ : GetOutputFileExtension(state, mediaSource);
}
state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.');
@@ -235,7 +234,7 @@ namespace Jellyfin.Api.Helpers
ApplyDeviceProfileSettings(state, dlnaManager, deviceManager, httpRequest, streamingRequest.DeviceProfileId, streamingRequest.Static);
var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
- ? GetOutputFileExtension(state)
+ ? GetOutputFileExtension(state, mediaSource)
: ("." + state.OutputContainer);
state.OutputFilePath = GetOutputFilePath(state, ext!, serverConfigurationManager, streamingRequest.DeviceId, streamingRequest.PlaySessionId);
@@ -312,7 +311,7 @@ namespace Jellyfin.Api.Helpers
responseHeaders.Add(
"contentFeatures.dlna.org",
- ContentFeatureBuilder.BuildVideoHeader(profile, state.OutputContainer, videoCodec, audioCodec, state.OutputWidth, state.OutputHeight, state.TargetVideoBitDepth, state.OutputVideoBitrate, state.TargetTimestamp, isStaticallyStreamed, state.RunTimeTicks, state.TargetVideoProfile, state.TargetVideoLevel, state.TargetFramerate, state.TargetPacketLength, state.TranscodeSeekInfo, state.IsTargetAnamorphic, state.IsTargetInterlaced, state.TargetRefFrames, state.TargetVideoStreamCount, state.TargetAudioStreamCount, state.TargetVideoCodecTag, state.IsTargetAVC).FirstOrDefault() ?? string.Empty);
+ ContentFeatureBuilder.BuildVideoHeader(profile, state.OutputContainer, videoCodec, audioCodec, state.OutputWidth, state.OutputHeight, state.TargetVideoBitDepth, state.OutputVideoBitrate, state.TargetTimestamp, isStaticallyStreamed, state.RunTimeTicks, state.TargetVideoProfile, state.TargetVideoRangeType, state.TargetVideoLevel, state.TargetFramerate, state.TargetPacketLength, state.TranscodeSeekInfo, state.IsTargetAnamorphic, state.IsTargetInterlaced, state.TargetRefFrames, state.TargetVideoStreamCount, state.TargetAudioStreamCount, state.TargetVideoCodecTag, state.IsTargetAVC).FirstOrDefault() ?? string.Empty);
}
}
@@ -409,8 +408,9 @@ namespace Jellyfin.Api.Helpers
/// Gets the output file extension.
/// </summary>
/// <param name="state">The state.</param>
+ /// <param name="mediaSource">The mediaSource.</param>
/// <returns>System.String.</returns>
- private static string? GetOutputFileExtension(StreamState state)
+ private static string? GetOutputFileExtension(StreamState state, MediaSourceInfo? mediaSource)
{
var ext = Path.GetExtension(state.RequestedUrl);
@@ -425,7 +425,7 @@ namespace Jellyfin.Api.Helpers
var videoCodec = state.Request.VideoCodec;
if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(videoCodec, "h265", StringComparison.OrdinalIgnoreCase))
+ string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
{
return ".ts";
}
@@ -474,6 +474,13 @@ namespace Jellyfin.Api.Helpers
}
}
+ // Fallback to the container of mediaSource
+ if (!string.IsNullOrEmpty(mediaSource?.Container))
+ {
+ var idx = mediaSource.Container.IndexOf(',', StringComparison.OrdinalIgnoreCase);
+ return '.' + (idx == -1 ? mediaSource.Container : mediaSource.Container[..idx]).Trim();
+ }
+
return null;
}
@@ -533,6 +540,7 @@ namespace Jellyfin.Api.Helpers
state.TargetVideoBitDepth,
state.OutputVideoBitrate,
state.TargetVideoProfile,
+ state.TargetVideoRangeType,
state.TargetVideoLevel,
state.TargetFramerate,
state.TargetPacketLength,
diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
index 416418dc6..c663c6e31 100644
--- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
+++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
@@ -8,6 +8,7 @@ using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Api.Extensions;
using Jellyfin.Api.Models.PlaybackDtos;
using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Data.Enums;
@@ -17,7 +18,6 @@ using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Entities;
@@ -46,7 +46,6 @@ namespace Jellyfin.Api.Helpers
private readonly IAttachmentExtractor _attachmentExtractor;
private readonly IApplicationPaths _appPaths;
- private readonly IAuthorizationContext _authorizationContext;
private readonly EncodingHelper _encodingHelper;
private readonly IFileSystem _fileSystem;
private readonly ILogger<TranscodingJobHelper> _logger;
@@ -55,6 +54,7 @@ namespace Jellyfin.Api.Helpers
private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly ISessionManager _sessionManager;
private readonly ILoggerFactory _loggerFactory;
+ private readonly IUserManager _userManager;
/// <summary>
/// Initializes a new instance of the <see cref="TranscodingJobHelper"/> class.
@@ -67,9 +67,9 @@ namespace Jellyfin.Api.Helpers
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
- /// <param name="authorizationContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
+ /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
public TranscodingJobHelper(
IAttachmentExtractor attachmentExtractor,
IApplicationPaths appPaths,
@@ -79,9 +79,9 @@ namespace Jellyfin.Api.Helpers
IMediaEncoder mediaEncoder,
IServerConfigurationManager serverConfigurationManager,
ISessionManager sessionManager,
- IAuthorizationContext authorizationContext,
EncodingHelper encodingHelper,
- ILoggerFactory loggerFactory)
+ ILoggerFactory loggerFactory,
+ IUserManager userManager)
{
_attachmentExtractor = attachmentExtractor;
_appPaths = appPaths;
@@ -91,9 +91,9 @@ namespace Jellyfin.Api.Helpers
_mediaEncoder = mediaEncoder;
_serverConfigurationManager = serverConfigurationManager;
_sessionManager = sessionManager;
- _authorizationContext = authorizationContext;
_encodingHelper = encodingHelper;
_loggerFactory = loggerFactory;
+ _userManager = userManager;
DeleteEncodedMediaCache();
@@ -512,8 +512,9 @@ namespace Jellyfin.Api.Helpers
if (state.VideoRequest != null && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
{
- var auth = await _authorizationContext.GetAuthorizationInfo(request).ConfigureAwait(false);
- if (auth.User != null && !auth.User.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
+ var userId = request.HttpContext.User.GetUserId();
+ var user = userId.Equals(default) ? null : _userManager.GetUserById(userId);
+ if (user != null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
{
this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state);
@@ -654,8 +655,8 @@ namespace Jellyfin.Api.Helpers
{
if (EnableThrottling(state))
{
- transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, new Logger<TranscodingThrottler>(new LoggerFactory()), _serverConfigurationManager, _fileSystem);
- state.TranscodingThrottler.Start();
+ transcodingJob.TranscodingThrottler = new TranscodingThrottler(transcodingJob, new Logger<TranscodingThrottler>(new LoggerFactory()), _serverConfigurationManager, _fileSystem, _mediaEncoder);
+ transcodingJob.TranscodingThrottler.Start();
}
}
@@ -663,18 +664,11 @@ namespace Jellyfin.Api.Helpers
{
var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
- // enable throttling when NOT using hardware acceleration
- if (string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType))
- {
- return state.InputProtocol == MediaProtocol.File &&
- state.RunTimeTicks.HasValue &&
- state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks &&
- state.IsInputVideo &&
- state.VideoType == VideoType.VideoFile &&
- !EncodingHelper.IsCopyCodec(state.OutputVideoCodec);
- }
-
- return false;
+ return state.InputProtocol == MediaProtocol.File &&
+ state.RunTimeTicks.HasValue &&
+ state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks &&
+ state.IsInputVideo &&
+ state.VideoType == VideoType.VideoFile;
}
/// <summary>
diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj
index 2b0436e34..7e64cf645 100644
--- a/Jellyfin.Api/Jellyfin.Api.csproj
+++ b/Jellyfin.Api/Jellyfin.Api.csproj
@@ -17,10 +17,10 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.3" />
+ <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.9" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
- <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.3.0" />
+ <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.4.0" />
</ItemGroup>
<ItemGroup>
@@ -36,7 +36,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
index 7a1ca252c..99376873c 100644
--- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
+++ b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
@@ -2,6 +2,7 @@
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
@@ -17,6 +18,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos
private readonly ILogger<TranscodingThrottler> _logger;
private readonly IConfigurationManager _config;
private readonly IFileSystem _fileSystem;
+ private readonly IMediaEncoder _mediaEncoder;
private Timer? _timer;
private bool _isPaused;
@@ -27,12 +29,14 @@ namespace Jellyfin.Api.Models.PlaybackDtos
/// <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)
+ /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
+ public TranscodingThrottler(TranscodingJobDto job, ILogger<TranscodingThrottler> logger, IConfigurationManager config, IFileSystem fileSystem, IMediaEncoder mediaEncoder)
{
_job = job;
_logger = logger;
_config = config;
_fileSystem = fileSystem;
+ _mediaEncoder = mediaEncoder;
}
/// <summary>
@@ -55,7 +59,8 @@ namespace Jellyfin.Api.Models.PlaybackDtos
try
{
- await _job.Process!.StandardInput.WriteLineAsync().ConfigureAwait(false);
+ var resumeKey = _mediaEncoder.IsPkeyPauseSupported ? "u" : Environment.NewLine;
+ await _job.Process!.StandardInput.WriteAsync(resumeKey).ConfigureAwait(false);
_isPaused = false;
}
catch (Exception ex)
@@ -125,11 +130,13 @@ namespace Jellyfin.Api.Models.PlaybackDtos
{
if (!_isPaused)
{
- _logger.LogDebug("Sending pause command to ffmpeg");
+ var pauseKey = _mediaEncoder.IsPkeyPauseSupported ? "p" : "c";
+
+ _logger.LogDebug("Sending pause command [{Key}] to ffmpeg", pauseKey);
try
{
- await _job.Process!.StandardInput.WriteAsync("c").ConfigureAwait(false);
+ await _job.Process!.StandardInput.WriteAsync(pauseKey).ConfigureAwait(false);
_isPaused = true;
}
catch (Exception ex)
diff --git a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs b/Jellyfin.Api/Models/StreamingDtos/StreamState.cs
index cbabf087b..8182e3c9e 100644
--- a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs
+++ b/Jellyfin.Api/Models/StreamingDtos/StreamState.cs
@@ -48,11 +48,6 @@ namespace Jellyfin.Api.Models.StreamingDtos
}
/// <summary>
- /// Gets or sets the transcoding throttler.
- /// </summary>
- public TranscodingThrottler? TranscodingThrottler { get; set; }
-
- /// <summary>
/// Gets the video request.
/// </summary>
public VideoRequestDto? VideoRequest => Request as VideoRequestDto;
@@ -174,7 +169,7 @@ namespace Jellyfin.Api.Models.StreamingDtos
/// <summary>
/// Disposes the stream state.
/// </summary>
- /// <param name="disposing">Whether the object is currently beeing disposed.</param>
+ /// <param name="disposing">Whether the object is currently being disposed.</param>
protected virtual void Dispose(bool disposing)
{
if (_disposed)
@@ -191,11 +186,8 @@ namespace Jellyfin.Api.Models.StreamingDtos
{
_mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).GetAwaiter().GetResult();
}
-
- TranscodingThrottler?.Dispose();
}
- TranscodingThrottler = null;
TranscodingJob = null;
_disposed = true;
diff --git a/Jellyfin.Api/Models/SyncPlayDtos/RemoveFromPlaylistRequestDto.cs b/Jellyfin.Api/Models/SyncPlayDtos/RemoveFromPlaylistRequestDto.cs
index 02ce5a048..226a584e1 100644
--- a/Jellyfin.Api/Models/SyncPlayDtos/RemoveFromPlaylistRequestDto.cs
+++ b/Jellyfin.Api/Models/SyncPlayDtos/RemoveFromPlaylistRequestDto.cs
@@ -17,9 +17,9 @@ namespace Jellyfin.Api.Models.SyncPlayDtos
}
/// <summary>
- /// Gets or sets the playlist identifiers ot the items. Ignored when clearing the playlist.
+ /// Gets or sets the playlist identifiers of the items. Ignored when clearing the playlist.
/// </summary>
- /// <value>The playlist identifiers ot the items.</value>
+ /// <value>The playlist identifiers of the items.</value>
public IReadOnlyList<Guid> PlaylistItemIds { get; set; }
/// <summary>
diff --git a/Jellyfin.Api/Results/OkResultOfT.cs b/Jellyfin.Api/Results/OkResultOfT.cs
new file mode 100644
index 000000000..f60cbbcee
--- /dev/null
+++ b/Jellyfin.Api/Results/OkResultOfT.cs
@@ -0,0 +1,21 @@
+#pragma warning disable SA1649 // File name should match type name.
+
+using Microsoft.AspNetCore.Mvc;
+
+namespace Jellyfin.Api.Results;
+
+/// <summary>
+/// Ok result with type specified.
+/// </summary>
+/// <typeparam name="T">The type to return.</typeparam>
+public class OkResult<T> : OkObjectResult
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OkResult{T}"/> class.
+ /// </summary>
+ /// <param name="value">The value to return.</param>
+ public OkResult(T value)
+ : base(value)
+ {
+ }
+}
diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj
index d39c75e36..47499e038 100644
--- a/Jellyfin.Data/Jellyfin.Data.csproj
+++ b/Jellyfin.Data/Jellyfin.Data.csproj
@@ -18,7 +18,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Data</PackageId>
- <VersionPrefix>10.8.0</VersionPrefix>
+ <VersionPrefix>10.9.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
@@ -34,7 +34,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
index 2188c0d99..cbbeee024 100644
--- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
+++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
@@ -18,8 +18,9 @@
<ItemGroup>
<PackageReference Include="BlurHashSharp" Version="1.2.0" />
<PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.2.0" />
- <PackageReference Include="SkiaSharp" Version="2.80.3" />
- <PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.80.3" />
+ <PackageReference Include="SkiaSharp" Version="2.88.2" />
+ <PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.2" />
+ <PackageReference Include="SkiaSharp.Svg" Version="1.60.0" />
</ItemGroup>
<ItemGroup>
@@ -35,7 +36,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs
index 1fa8e570d..84b261b54 100644
--- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs
+++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs
@@ -3,14 +3,14 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using BlurHashSharp.SkiaSharp;
-using Diacritics.Extensions;
+using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.Drawing;
using Microsoft.Extensions.Logging;
using SkiaSharp;
-using static Jellyfin.Drawing.Skia.SkiaHelper;
+using SKSvg = SkiaSharp.Extended.Svg.SKSvg;
namespace Jellyfin.Drawing.Skia
{
@@ -19,8 +19,7 @@ namespace Jellyfin.Drawing.Skia
/// </summary>
public class SkiaEncoder : IImageEncoder
{
- private static readonly HashSet<string> _transparentImageTypes
- = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
+ private static readonly HashSet<string> _transparentImageTypes = new(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
private readonly ILogger<SkiaEncoder> _logger;
private readonly IApplicationPaths _appPaths;
@@ -71,7 +70,7 @@ namespace Jellyfin.Drawing.Skia
/// <inheritdoc/>
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats
- => new HashSet<ImageFormat>() { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
+ => new HashSet<ImageFormat> { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
/// <summary>
/// Check if the native lib is available.
@@ -109,9 +108,7 @@ namespace Jellyfin.Drawing.Skia
}
/// <inheritdoc />
- /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="FileNotFoundException">The path is not valid.</exception>
- /// <exception cref="SkiaCodecException">The file at the specified path could not be used to generate a codec.</exception>
public ImageDimensions GetImageSize(string path)
{
if (!File.Exists(path))
@@ -119,12 +116,27 @@ namespace Jellyfin.Drawing.Skia
throw new FileNotFoundException("File not found", path);
}
- using var codec = SKCodec.Create(path, out SKCodecResult result);
- EnsureSuccess(result);
-
- var info = codec.Info;
+ var extension = Path.GetExtension(path.AsSpan());
+ if (extension.Equals(".svg", StringComparison.OrdinalIgnoreCase))
+ {
+ var svg = new SKSvg();
+ svg.Load(path);
+ return new ImageDimensions(Convert.ToInt32(svg.Picture.CullRect.Width), Convert.ToInt32(svg.Picture.CullRect.Height));
+ }
- return new ImageDimensions(info.Width, info.Height);
+ using var codec = SKCodec.Create(path, out SKCodecResult result);
+ switch (result)
+ {
+ case SKCodecResult.Success:
+ var info = codec.Info;
+ return new ImageDimensions(info.Width, info.Height);
+ case SKCodecResult.Unimplemented:
+ _logger.LogDebug("Image format not supported: {FilePath}", path);
+ return new ImageDimensions(0, 0);
+ default:
+ _logger.LogError("Unable to determine image dimensions for {FilePath}: {SkCodecResult}", path, result);
+ return new ImageDimensions(0, 0);
+ }
}
/// <inheritdoc />
@@ -133,9 +145,18 @@ namespace Jellyfin.Drawing.Skia
/// <exception cref="SkiaCodecException">The file at the specified path could not be used to generate a codec.</exception>
public string GetImageBlurHash(int xComp, int yComp, string path)
{
- if (path == null)
+ ArgumentNullException.ThrowIfNull(path);
+
+ if (path.Length == 0)
+ {
+ throw new ArgumentException("String can't be empty", nameof(path));
+ }
+
+ var extension = Path.GetExtension(path.AsSpan()).TrimStart('.');
+ if (!SupportedInputFormats.Contains(extension, StringComparison.OrdinalIgnoreCase))
{
- throw new ArgumentNullException(nameof(path));
+ _logger.LogDebug("Unable to compute blur hash due to unsupported format: {ImagePath}", path);
+ return string.Empty;
}
// Any larger than 128x128 is too slow and there's no visually discernible difference
@@ -378,6 +399,13 @@ namespace Jellyfin.Drawing.Skia
throw new ArgumentException("String can't be empty.", nameof(outputPath));
}
+ var inputFormat = Path.GetExtension(inputPath.AsSpan()).TrimStart('.');
+ if (!SupportedInputFormats.Contains(inputFormat, StringComparison.OrdinalIgnoreCase))
+ {
+ _logger.LogDebug("Unable to encode image due to unsupported format: {ImagePath}", inputPath);
+ return inputPath;
+ }
+
var skiaOutputFormat = GetImageFormat(outputFormat);
var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor);
diff --git a/Jellyfin.Drawing.Skia/SkiaHelper.cs b/Jellyfin.Drawing.Skia/SkiaHelper.cs
index 35dcebdab..0478fc7c3 100644
--- a/Jellyfin.Drawing.Skia/SkiaHelper.cs
+++ b/Jellyfin.Drawing.Skia/SkiaHelper.cs
@@ -9,24 +9,11 @@ namespace Jellyfin.Drawing.Skia
public static class SkiaHelper
{
/// <summary>
- /// Ensures the result is a success
- /// by throwing an exception when that's not the case.
- /// </summary>
- /// <param name="result">The result returned by Skia.</param>
- public static void EnsureSuccess(SKCodecResult result)
- {
- if (result != SKCodecResult.Success)
- {
- throw new SkiaCodecException(result);
- }
- }
-
- /// <summary>
/// Gets the next valid image as a bitmap.
/// </summary>
/// <param name="skiaEncoder">The current skia encoder.</param>
/// <param name="paths">The list of image paths.</param>
- /// <param name="currentIndex">The current checked indes.</param>
+ /// <param name="currentIndex">The current checked index.</param>
/// <param name="newIndex">The new index.</param>
/// <returns>A valid bitmap, or null if no bitmap exists after <c>currentIndex</c>.</returns>
public static SKBitmap? GetNextValidImage(SkiaEncoder skiaEncoder, IReadOnlyList<string> paths, int currentIndex, out int newIndex)
diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs
index 6bece9db6..b55a99405 100644
--- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs
+++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs
@@ -29,10 +29,7 @@ namespace Jellyfin.Drawing.Skia
/// <returns>The image format.</returns>
public static SKEncodedImageFormat GetEncodedFormat(string outputPath)
{
- if (outputPath == null)
- {
- throw new ArgumentNullException(nameof(outputPath));
- }
+ ArgumentNullException.ThrowIfNull(outputPath);
var ext = Path.GetExtension(outputPath);
diff --git a/Jellyfin.Networking/Configuration/NetworkConfiguration.cs b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs
index 61db223d9..361dbc814 100644
--- a/Jellyfin.Networking/Configuration/NetworkConfiguration.cs
+++ b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs
@@ -193,7 +193,7 @@ namespace Jellyfin.Networking.Configuration
public bool AutoDiscovery { get; set; } = true;
/// <summary>
- /// Gets or sets the filter for remote IP connectivity. Used in conjuntion with <seealso cref="IsRemoteIPFilterBlacklist"/>.
+ /// Gets or sets the filter for remote IP connectivity. Used in conjunction with <seealso cref="IsRemoteIPFilterBlacklist"/>.
/// </summary>
public string[] RemoteIPFilter { get; set; } = Array.Empty<string>();
diff --git a/Jellyfin.Networking/Jellyfin.Networking.csproj b/Jellyfin.Networking/Jellyfin.Networking.csproj
index 0edf4adcc..d05072152 100644
--- a/Jellyfin.Networking/Jellyfin.Networking.csproj
+++ b/Jellyfin.Networking/Jellyfin.Networking.csproj
@@ -16,7 +16,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs
index b16dc5390..9e06cdfe7 100644
--- a/Jellyfin.Networking/Manager/NetworkManager.cs
+++ b/Jellyfin.Networking/Manager/NetworkManager.cs
@@ -353,10 +353,7 @@ namespace Jellyfin.Networking.Manager
public string GetBindInterface(IPObject source, out int? port)
{
port = null;
- if (source == null)
- {
- throw new ArgumentNullException(nameof(source));
- }
+ ArgumentNullException.ThrowIfNull(source);
// Do we have a source?
bool haveSource = !source.Address.Equals(IPAddress.None);
@@ -464,43 +461,23 @@ namespace Jellyfin.Networking.Manager
/// <inheritdoc/>
public bool IsInLocalNetwork(IPObject address)
{
- if (address == null)
- {
- throw new ArgumentNullException(nameof(address));
- }
-
- if (address.Equals(IPAddress.None))
- {
- return false;
- }
-
- // See conversation at https://github.com/jellyfin/jellyfin/pull/3515.
- if (TrustAllIP6Interfaces && address.AddressFamily == AddressFamily.InterNetworkV6)
- {
- return true;
- }
-
- // As private addresses can be redefined by Configuration.LocalNetworkAddresses
- return address.IsLoopback() || (_lanSubnets.ContainsAddress(address) && !_excludedSubnets.ContainsAddress(address));
+ return IsInLocalNetwork(address.Address);
}
/// <inheritdoc/>
public bool IsInLocalNetwork(string address)
{
- if (IPHost.TryParse(address, out IPHost ep))
- {
- return _lanSubnets.ContainsAddress(ep) && !_excludedSubnets.ContainsAddress(ep);
- }
-
- return false;
+ return IPHost.TryParse(address, out IPHost ipHost) && IsInLocalNetwork(ipHost);
}
/// <inheritdoc/>
public bool IsInLocalNetwork(IPAddress address)
{
- if (address == null)
+ ArgumentNullException.ThrowIfNull(address);
+
+ if (address.Equals(IPAddress.None))
{
- throw new ArgumentNullException(nameof(address));
+ return false;
}
// See conversation at https://github.com/jellyfin/jellyfin/pull/3515.
@@ -510,16 +487,13 @@ namespace Jellyfin.Networking.Manager
}
// As private addresses can be redefined by Configuration.LocalNetworkAddresses
- return _lanSubnets.ContainsAddress(address) && !_excludedSubnets.ContainsAddress(address);
+ return IPAddress.IsLoopback(address) || (_lanSubnets.ContainsAddress(address) && !_excludedSubnets.ContainsAddress(address));
}
/// <inheritdoc/>
public bool IsPrivateAddressRange(IPObject address)
{
- if (address == null)
- {
- throw new ArgumentNullException(nameof(address));
- }
+ ArgumentNullException.ThrowIfNull(address);
// See conversation at https://github.com/jellyfin/jellyfin/pull/3515.
if (TrustAllIP6Interfaces && address.AddressFamily == AddressFamily.InterNetworkV6)
@@ -961,7 +935,7 @@ namespace Jellyfin.Networking.Manager
// Add virtual machine interface names to the list of bind exclusions, so that they are auto-excluded.
if (config.IgnoreVirtualInterfaces)
{
- // each virtual interface name must be pre-pended with the exclusion symbol !
+ // each virtual interface name must be prepended with the exclusion symbol !
var virtualInterfaceNames = config.VirtualInterfaceNames.Split(',').Select(p => "!" + p).ToArray();
if (lanAddresses.Length > 0)
{
diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs
index 3203bed18..0728f1179 100644
--- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs
+++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs
@@ -201,10 +201,7 @@ namespace Jellyfin.Server.Implementations.Devices
/// <inheritdoc />
public bool CanAccessDevice(User user, string deviceId)
{
- if (user == null)
- {
- throw new ArgumentNullException(nameof(user));
- }
+ ArgumentNullException.ThrowIfNull(user);
if (string.IsNullOrEmpty(deviceId))
{
diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
index e68b2ed7f..83b226278 100644
--- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
+++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
@@ -17,7 +17,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
@@ -27,13 +27,13 @@
<ItemGroup>
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.3" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.3" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.3">
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.9" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.9" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.9">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.3">
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.9">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
diff --git a/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs b/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs
index d59d36e88..9f813f532 100644
--- a/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs
+++ b/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
+using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using Microsoft.AspNetCore.Http;
@@ -16,11 +17,16 @@ namespace Jellyfin.Server.Implementations.Security
{
private readonly JellyfinDbProvider _jellyfinDbProvider;
private readonly IUserManager _userManager;
+ private readonly IServerApplicationHost _serverApplicationHost;
- public AuthorizationContext(JellyfinDbProvider jellyfinDb, IUserManager userManager)
+ public AuthorizationContext(
+ JellyfinDbProvider jellyfinDb,
+ IUserManager userManager,
+ IServerApplicationHost serverApplicationHost)
{
_jellyfinDbProvider = jellyfinDb;
_userManager = userManager;
+ _serverApplicationHost = serverApplicationHost;
}
public Task<AuthorizationInfo> GetAuthorizationInfo(HttpContext requestContext)
@@ -187,17 +193,17 @@ namespace Jellyfin.Server.Implementations.Security
authInfo.Token = key.AccessToken;
if (string.IsNullOrWhiteSpace(authInfo.DeviceId))
{
- authInfo.DeviceId = string.Empty;
+ authInfo.DeviceId = _serverApplicationHost.SystemId;
}
if (string.IsNullOrWhiteSpace(authInfo.Device))
{
- authInfo.Device = string.Empty;
+ authInfo.Device = _serverApplicationHost.Name;
}
if (string.IsNullOrWhiteSpace(authInfo.Version))
{
- authInfo.Version = string.Empty;
+ authInfo.Version = _serverApplicationHost.ApplicationVersionString;
}
authInfo.IsApiKey = true;
diff --git a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs
index 5e84255f9..4fda8f5a4 100644
--- a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs
+++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs
@@ -67,7 +67,7 @@ namespace Jellyfin.Server.Implementations.Users
else if (string.Equals(
spr.Pin.Replace("-", string.Empty, StringComparison.Ordinal),
pin.Replace("-", string.Empty, StringComparison.Ordinal),
- StringComparison.OrdinalIgnoreCase))
+ StringComparison.Ordinal))
{
var resetUser = userManager.GetUserByName(spr.UserName)
?? throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found");
diff --git a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs
index f5d38db20..65edb30ad 100644
--- a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs
+++ b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs
@@ -79,7 +79,7 @@ namespace Jellyfin.Server.Implementations.Users
}
/// <inheritdoc />
- public void SetCustomItemDisplayPreferences(Guid userId, Guid itemId, string client, Dictionary<string, string> customPreferences)
+ public void SetCustomItemDisplayPreferences(Guid userId, Guid itemId, string client, Dictionary<string, string?> customPreferences)
{
var existingPrefs = _dbContext.CustomItemDisplayPreferences
.AsQueryable()
diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs
index 2100fa6d5..ed90e81c6 100644
--- a/Jellyfin.Server.Implementations/Users/UserManager.cs
+++ b/Jellyfin.Server.Implementations/Users/UserManager.cs
@@ -130,10 +130,7 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public async Task RenameUser(User user, string newName)
{
- if (user == null)
- {
- throw new ArgumentNullException(nameof(user));
- }
+ ArgumentNullException.ThrowIfNull(user);
ThrowIfInvalidUsername(newName);
@@ -267,10 +264,7 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public async Task ChangePassword(User user, string newPassword)
{
- if (user == null)
- {
- throw new ArgumentNullException(nameof(user));
- }
+ ArgumentNullException.ThrowIfNull(user);
await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false);
await UpdateUserAsync(user).ConfigureAwait(false);
@@ -326,10 +320,10 @@ namespace Jellyfin.Server.Implementations.Users
EnableNextEpisodeAutoPlay = user.EnableNextEpisodeAutoPlay,
RememberSubtitleSelections = user.RememberSubtitleSelections,
SubtitleLanguagePreference = user.SubtitleLanguagePreference ?? string.Empty,
- OrderedViews = user.GetPreference(PreferenceKind.OrderedViews),
- GroupedFolders = user.GetPreference(PreferenceKind.GroupedFolders),
- MyMediaExcludes = user.GetPreference(PreferenceKind.MyMediaExcludes),
- LatestItemsExcludes = user.GetPreference(PreferenceKind.LatestItemExcludes)
+ OrderedViews = user.GetPreferenceValues<Guid>(PreferenceKind.OrderedViews),
+ GroupedFolders = user.GetPreferenceValues<Guid>(PreferenceKind.GroupedFolders),
+ MyMediaExcludes = user.GetPreferenceValues<Guid>(PreferenceKind.MyMediaExcludes),
+ LatestItemsExcludes = user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes)
},
Policy = new UserPolicy
{
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index 3df8481fd..66fa3bc31 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -440,6 +440,12 @@ namespace Jellyfin.Server.Extensions
.Cast<IOpenApiAny>()
.ToArray()
});
+
+ // Swashbuckle doesn't use JsonOptions to describe responses, so we need to manually describe it.
+ options.MapType<Version>(() => new OpenApiSchema
+ {
+ Type = "string"
+ });
}
}
}
diff --git a/Jellyfin.Server/Filters/AdditionalModelFilter.cs b/Jellyfin.Server/Filters/AdditionalModelFilter.cs
index 87a59e0b4..487948f81 100644
--- a/Jellyfin.Server/Filters/AdditionalModelFilter.cs
+++ b/Jellyfin.Server/Filters/AdditionalModelFilter.cs
@@ -1,4 +1,8 @@
-using MediaBrowser.Common.Plugins;
+using System;
+using Jellyfin.Extensions;
+using Jellyfin.Server.Migrations;
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.ApiClient;
using MediaBrowser.Model.Entities;
@@ -14,6 +18,19 @@ namespace Jellyfin.Server.Filters
/// </summary>
public class AdditionalModelFilter : IDocumentFilter
{
+ // Array of options that should not be visible in the api spec.
+ private static readonly Type[] _ignoredConfigurations = { typeof(MigrationOptions) };
+ private readonly IServerConfigurationManager _serverConfigurationManager;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AdditionalModelFilter"/> class.
+ /// </summary>
+ /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
+ public AdditionalModelFilter(IServerConfigurationManager serverConfigurationManager)
+ {
+ _serverConfigurationManager = serverConfigurationManager;
+ }
+
/// <inheritdoc />
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
@@ -29,6 +46,16 @@ namespace Jellyfin.Server.Filters
context.SchemaGenerator.GenerateSchema(typeof(SessionMessageType), context.SchemaRepository);
context.SchemaGenerator.GenerateSchema(typeof(ServerDiscoveryInfo), context.SchemaRepository);
+
+ foreach (var configuration in _serverConfigurationManager.GetConfigurationStores())
+ {
+ if (_ignoredConfigurations.IndexOf(configuration.ConfigurationType) != -1)
+ {
+ continue;
+ }
+
+ context.SchemaGenerator.GenerateSchema(configuration.ConfigurationType, context.SchemaRepository);
+ }
}
}
}
diff --git a/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs b/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs
index 7e7e4ca95..bb264d512 100644
--- a/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs
+++ b/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs
@@ -69,15 +69,8 @@ namespace Jellyfin.Server.Infrastructure
/// <inheritdoc />
protected override Task WriteFileAsync(ActionContext context, PhysicalFileResult result, RangeItemHeaderValue? range, long rangeLength)
{
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- if (result == null)
- {
- throw new ArgumentNullException(nameof(result));
- }
+ ArgumentNullException.ThrowIfNull(context);
+ ArgumentNullException.ThrowIfNull(result);
if (range != null && rangeLength == 0)
{
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index fb021f3af..b2d79050b 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -29,26 +29,26 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
- <PackageReference Include="CommandLineParser" Version="2.8.0" />
+ <PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
- <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.3" />
- <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.3" />
+ <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.9" />
+ <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.9" />
<PackageReference Include="prometheus-net" Version="6.0.0" />
<PackageReference Include="prometheus-net.AspNetCore" Version="6.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
- <PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />
+ <PackageReference Include="Serilog.Settings.Configuration" Version="3.4.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
- <PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
+ <PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.Graylog" Version="2.3.0" />
- <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.7" />
+ <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.2" />
</ItemGroup>
<ItemGroup>
diff --git a/Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs b/Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs
index da9b69136..1c25696cd 100644
--- a/Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs
+++ b/Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs
@@ -19,41 +19,44 @@ namespace Jellyfin.Server.Middleware
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)
+ ILogger<ResponseTimeMiddleware> logger)
{
_next = next;
_logger = logger;
-
- _enableWarning = serverConfigurationManager.Configuration.EnableSlowResponseWarning;
- _warningThreshold = serverConfigurationManager.Configuration.SlowResponseThresholdMs;
}
/// <summary>
/// Invoke request.
/// </summary>
/// <param name="context">Request context.</param>
+ /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <returns>Task.</returns>
- public async Task Invoke(HttpContext context)
+ public async Task Invoke(HttpContext context, IServerConfigurationManager serverConfigurationManager)
{
var watch = new Stopwatch();
watch.Start();
-
+ var enableWarning = serverConfigurationManager.Configuration.EnableSlowResponseWarning;
+ var warningThreshold = serverConfigurationManager.Configuration.SlowResponseThresholdMs;
context.Response.OnStarting(() =>
{
watch.Stop();
- LogWarning(context, 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.GetNormalizedRemoteIp(),
+ watch.Elapsed,
+ context.Response.StatusCode);
+ }
+
var responseTimeForCompleteRequest = watch.ElapsedMilliseconds;
context.Response.Headers[ResponseHeaderResponseTime] = responseTimeForCompleteRequest.ToString(CultureInfo.InvariantCulture);
return Task.CompletedTask;
@@ -62,18 +65,5 @@ namespace Jellyfin.Server.Middleware
// 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.GetNormalizedRemoteIp(),
- watch.Elapsed,
- context.Response.StatusCode);
- }
- }
}
}
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs
index afd7aee5d..ba0e33585 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs
@@ -5,6 +5,7 @@ using Emby.Server.Implementations.Data;
using Jellyfin.Data.Entities.Security;
using Jellyfin.Server.Implementations;
using MediaBrowser.Controller;
+using MediaBrowser.Controller.Library;
using Microsoft.Extensions.Logging;
using SQLitePCL.pretty;
@@ -20,6 +21,7 @@ namespace Jellyfin.Server.Migrations.Routines
private readonly ILogger<MigrateAuthenticationDb> _logger;
private readonly JellyfinDbProvider _dbProvider;
private readonly IServerApplicationPaths _appPaths;
+ private readonly IUserManager _userManager;
/// <summary>
/// Initializes a new instance of the <see cref="MigrateAuthenticationDb"/> class.
@@ -27,11 +29,17 @@ namespace Jellyfin.Server.Migrations.Routines
/// <param name="logger">The logger.</param>
/// <param name="dbProvider">The database provider.</param>
/// <param name="appPaths">The server application paths.</param>
- public MigrateAuthenticationDb(ILogger<MigrateAuthenticationDb> logger, JellyfinDbProvider dbProvider, IServerApplicationPaths appPaths)
+ /// <param name="userManager">The user manager.</param>
+ public MigrateAuthenticationDb(
+ ILogger<MigrateAuthenticationDb> logger,
+ JellyfinDbProvider dbProvider,
+ IServerApplicationPaths appPaths,
+ IUserManager userManager)
{
_logger = logger;
_dbProvider = dbProvider;
_appPaths = appPaths;
+ _userManager = userManager;
}
/// <inheritdoc />
@@ -74,6 +82,14 @@ namespace Jellyfin.Server.Migrations.Routines
}
else
{
+ var userId = new Guid(row[6].ToString());
+ var user = _userManager.GetUserById(userId);
+ if (user is null)
+ {
+ // User doesn't exist, don't bring over the device.
+ continue;
+ }
+
dbContext.Devices.Add(new Device(
new Guid(row[6].ToString()),
row[3].ToString(),
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index 1323d8a48..a6f0b705d 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -243,7 +243,7 @@ namespace Jellyfin.Server
}
}
- appHost.Dispose();
+ await appHost.DisposeAsync().ConfigureAwait(false);
}
if (_restartOnShutdown)
@@ -545,12 +545,14 @@ namespace Jellyfin.Server
const string ResourcePath = "Jellyfin.Server.Resources.Configuration.logging.json";
Stream resource = typeof(Program).Assembly.GetManifestResourceStream(ResourcePath)
?? throw new InvalidOperationException($"Invalid resource path: '{ResourcePath}'");
- Stream dst = new FileStream(configPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
await using (resource.ConfigureAwait(false))
- await using (dst.ConfigureAwait(false))
{
- // Copy the resource contents to the expected file path for the config file
- await resource.CopyToAsync(dst).ConfigureAwait(false);
+ Stream dst = new FileStream(configPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
+ await using (dst.ConfigureAwait(false))
+ {
+ // Copy the resource contents to the expected file path for the config file
+ await resource.CopyToAsync(dst).ConfigureAwait(false);
+ }
}
}
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index 8eb5f2196..1954a5c55 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -1,4 +1,5 @@
using System;
+using System.Globalization;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
@@ -103,6 +104,22 @@ namespace Jellyfin.Server
})
.ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate);
+ services.AddHttpClient(NamedClient.Dlna, c =>
+ {
+ c.DefaultRequestHeaders.UserAgent.ParseAdd(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}/{1} UPnP/1.0 {2}/{3}",
+ MediaBrowser.Common.System.OperatingSystem.Name,
+ Environment.OSVersion,
+ _serverApplicationHost.Name,
+ _serverApplicationHost.ApplicationVersionString));
+
+ c.DefaultRequestHeaders.Add("CPFN.UPNP.ORG", _serverApplicationHost.FriendlyName); // Required for UPnP DeviceArchitecture v2.0
+ c.DefaultRequestHeaders.Add("FriendlyName.DLNA.ORG", _serverApplicationHost.FriendlyName); // REVIEW: where does this come from?
+ })
+ .ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate);
+
services.AddHealthChecks()
.AddDbContextCheck<JellyfinDb>();
diff --git a/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs b/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs
index 2df87d879..90b1ff70c 100644
--- a/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs
+++ b/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs
@@ -1,22 +1,33 @@
-#nullable disable
-#pragma warning disable CS1591
-
using System;
namespace MediaBrowser.Common.Configuration
{
+ /// <summary>
+ /// <see cref="EventArgs" /> for the ConfigurationUpdated event.
+ /// </summary>
public class ConfigurationUpdateEventArgs : EventArgs
{
/// <summary>
- /// Gets or sets the key.
+ /// Initializes a new instance of the <see cref="ConfigurationUpdateEventArgs"/> class.
+ /// </summary>
+ /// <param name="key">The configuration key.</param>
+ /// <param name="newConfiguration">The new configuration.</param>
+ public ConfigurationUpdateEventArgs(string key, object newConfiguration)
+ {
+ Key = key;
+ NewConfiguration = newConfiguration;
+ }
+
+ /// <summary>
+ /// Gets the key.
/// </summary>
/// <value>The key.</value>
- public string Key { get; set; }
+ public string Key { get; }
/// <summary>
- /// Gets or sets the new configuration.
+ /// Gets the new configuration.
/// </summary>
/// <value>The new configuration.</value>
- public object NewConfiguration { get; set; }
+ public object NewConfiguration { get; }
}
}
diff --git a/MediaBrowser.Common/Configuration/EncodingConfigurationExtensions.cs b/MediaBrowser.Common/Configuration/EncodingConfigurationExtensions.cs
index 89740ae08..70a4fe409 100644
--- a/MediaBrowser.Common/Configuration/EncodingConfigurationExtensions.cs
+++ b/MediaBrowser.Common/Configuration/EncodingConfigurationExtensions.cs
@@ -32,7 +32,7 @@ namespace MediaBrowser.Common.Configuration
var transcodingTempPath = configurationManager.GetEncodingOptions().TranscodingTempPath;
if (string.IsNullOrEmpty(transcodingTempPath))
{
- transcodingTempPath = Path.Combine(configurationManager.CommonApplicationPaths.ProgramDataPath, "transcodes");
+ transcodingTempPath = Path.Combine(configurationManager.CommonApplicationPaths.CachePath, "transcodes");
}
// Make sure the directory exists
diff --git a/MediaBrowser.Common/Configuration/IApplicationPaths.cs b/MediaBrowser.Common/Configuration/IApplicationPaths.cs
index 1370e6d79..57c654667 100644
--- a/MediaBrowser.Common/Configuration/IApplicationPaths.cs
+++ b/MediaBrowser.Common/Configuration/IApplicationPaths.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
namespace MediaBrowser.Common.Configuration
{
/// <summary>
diff --git a/MediaBrowser.Common/Configuration/IConfigurationManager.cs b/MediaBrowser.Common/Configuration/IConfigurationManager.cs
index fc63d9350..e6696a571 100644
--- a/MediaBrowser.Common/Configuration/IConfigurationManager.cs
+++ b/MediaBrowser.Common/Configuration/IConfigurationManager.cs
@@ -61,6 +61,12 @@ namespace MediaBrowser.Common.Configuration
object GetConfiguration(string key);
/// <summary>
+ /// Gets the array of coniguration stores.
+ /// </summary>
+ /// <returns>Array of ConfigurationStore.</returns>
+ ConfigurationStore[] GetConfigurationStores();
+
+ /// <summary>
/// Gets the type of the configuration.
/// </summary>
/// <param name="key">The key.</param>
diff --git a/MediaBrowser.Common/Extensions/BaseExtensions.cs b/MediaBrowser.Common/Extensions/BaseExtensions.cs
index 08964420e..e3775021e 100644
--- a/MediaBrowser.Common/Extensions/BaseExtensions.cs
+++ b/MediaBrowser.Common/Extensions/BaseExtensions.cs
@@ -31,11 +31,7 @@ namespace MediaBrowser.Common.Extensions
public static Guid GetMD5(this string str)
{
#pragma warning disable CA5351
- using (var provider = MD5.Create())
- {
- return new Guid(provider.ComputeHash(Encoding.Unicode.GetBytes(str)));
- }
-
+ return new Guid(MD5.HashData(Encoding.Unicode.GetBytes(str)));
#pragma warning restore CA5351
}
}
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index 35231a792..7a55f398a 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Common</PackageId>
- <VersionPrefix>10.8.0</VersionPrefix>
+ <VersionPrefix>10.9.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
@@ -54,7 +54,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/MediaBrowser.Common/Net/IPNetAddress.cs b/MediaBrowser.Common/Net/IPNetAddress.cs
index f1428d4be..98d1c2d97 100644
--- a/MediaBrowser.Common/Net/IPNetAddress.cs
+++ b/MediaBrowser.Common/Net/IPNetAddress.cs
@@ -160,10 +160,7 @@ namespace MediaBrowser.Common.Net
/// <inheritdoc/>
public override bool Contains(IPAddress address)
{
- if (address == null)
- {
- throw new ArgumentNullException(nameof(address));
- }
+ ArgumentNullException.ThrowIfNull(address);
if (address.IsIPv4MappedToIPv6)
{
diff --git a/MediaBrowser.Common/Net/IPObject.cs b/MediaBrowser.Common/Net/IPObject.cs
index bd5368882..37385972f 100644
--- a/MediaBrowser.Common/Net/IPObject.cs
+++ b/MediaBrowser.Common/Net/IPObject.cs
@@ -55,17 +55,14 @@ namespace MediaBrowser.Common.Net
/// <returns>IPAddress.</returns>
public static (IPAddress Address, byte PrefixLength) NetworkAddressOf(IPAddress address, byte prefixLength)
{
- if (address == null)
- {
- throw new ArgumentNullException(nameof(address));
- }
+ ArgumentNullException.ThrowIfNull(address);
if (address.IsIPv4MappedToIPv6)
{
address = address.MapToIPv4();
}
- if (IsLoopback(address))
+ if (IPAddress.IsLoopback(address))
{
return (address, prefixLength);
}
@@ -103,41 +100,13 @@ namespace MediaBrowser.Common.Net
}
/// <summary>
- /// Tests to see if the ip address is a Loopback address.
- /// </summary>
- /// <param name="address">Value to test.</param>
- /// <returns>True if it is.</returns>
- public static bool IsLoopback(IPAddress address)
- {
- if (address == null)
- {
- throw new ArgumentNullException(nameof(address));
- }
-
- if (!address.Equals(IPAddress.None))
- {
- if (address.IsIPv4MappedToIPv6)
- {
- address = address.MapToIPv4();
- }
-
- return address.Equals(IPAddress.Loopback) || address.Equals(IPAddress.IPv6Loopback);
- }
-
- return false;
- }
-
- /// <summary>
/// Tests to see if the ip address is an IP6 address.
/// </summary>
/// <param name="address">Value to test.</param>
/// <returns>True if it is.</returns>
public static bool IsIP6(IPAddress address)
{
- if (address == null)
- {
- throw new ArgumentNullException(nameof(address));
- }
+ ArgumentNullException.ThrowIfNull(address);
if (address.IsIPv4MappedToIPv6)
{
@@ -154,10 +123,7 @@ namespace MediaBrowser.Common.Net
/// <returns>True if it contains a private address.</returns>
public static bool IsPrivateAddressRange(IPAddress address)
{
- if (address == null)
- {
- throw new ArgumentNullException(nameof(address));
- }
+ ArgumentNullException.ThrowIfNull(address);
if (!address.Equals(IPAddress.None))
{
@@ -204,10 +170,7 @@ namespace MediaBrowser.Common.Net
/// </remarks>
public static bool IsIPv6LinkLocal(IPAddress address)
{
- if (address == null)
- {
- throw new ArgumentNullException(nameof(address));
- }
+ ArgumentNullException.ThrowIfNull(address);
if (address.IsIPv4MappedToIPv6)
{
@@ -251,10 +214,7 @@ namespace MediaBrowser.Common.Net
/// <returns>Byte CIDR representing the mask.</returns>
public static byte MaskToCidr(IPAddress mask)
{
- if (mask == null)
- {
- throw new ArgumentNullException(nameof(mask));
- }
+ ArgumentNullException.ThrowIfNull(mask);
byte cidrnet = 0;
if (!mask.Equals(IPAddress.Any))
@@ -295,7 +255,7 @@ namespace MediaBrowser.Common.Net
/// <returns>True if it is.</returns>
public virtual bool IsLoopback()
{
- return IsLoopback(Address);
+ return IPAddress.IsLoopback(Address);
}
/// <summary>
diff --git a/MediaBrowser.Common/Net/NamedClient.cs b/MediaBrowser.Common/Net/NamedClient.cs
index 0f6161c32..a6cacd4f1 100644
--- a/MediaBrowser.Common/Net/NamedClient.cs
+++ b/MediaBrowser.Common/Net/NamedClient.cs
@@ -14,5 +14,10 @@
/// Gets the value for the MusicBrainz named http client.
/// </summary>
public const string MusicBrainz = nameof(MusicBrainz);
+
+ /// <summary>
+ /// Gets the value for the DLNA named http client.
+ /// </summary>
+ public const string Dlna = nameof(Dlna);
}
}
diff --git a/MediaBrowser.Common/Net/NetworkExtensions.cs b/MediaBrowser.Common/Net/NetworkExtensions.cs
index 264bfacb4..7ad005854 100644
--- a/MediaBrowser.Common/Net/NetworkExtensions.cs
+++ b/MediaBrowser.Common/Net/NetworkExtensions.cs
@@ -61,10 +61,7 @@ namespace MediaBrowser.Common.Net
return false;
}
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
+ ArgumentNullException.ThrowIfNull(item);
if (item.IsIPv4MappedToIPv6)
{
@@ -96,10 +93,7 @@ namespace MediaBrowser.Common.Net
return false;
}
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
+ ArgumentNullException.ThrowIfNull(item);
foreach (var i in source)
{
@@ -153,10 +147,7 @@ namespace MediaBrowser.Common.Net
/// <returns>Collection{IPObject} object containing the subnets.</returns>
public static Collection<IPObject> AsNetworks(this Collection<IPObject> source)
{
- if (source == null)
- {
- throw new ArgumentNullException(nameof(source));
- }
+ ArgumentNullException.ThrowIfNull(source);
Collection<IPObject> res = new Collection<IPObject>();
@@ -239,10 +230,7 @@ namespace MediaBrowser.Common.Net
return new Collection<IPObject>();
}
- if (target == null)
- {
- throw new ArgumentNullException(nameof(target));
- }
+ ArgumentNullException.ThrowIfNull(target);
Collection<IPObject> nc = new Collection<IPObject>();
diff --git a/MediaBrowser.Common/Plugins/BasePluginOfT.cs b/MediaBrowser.Common/Plugins/BasePluginOfT.cs
index afda83a7c..0da5592d3 100644
--- a/MediaBrowser.Common/Plugins/BasePluginOfT.cs
+++ b/MediaBrowser.Common/Plugins/BasePluginOfT.cs
@@ -164,10 +164,7 @@ namespace MediaBrowser.Common.Plugins
/// <inheritdoc />
public virtual void UpdateConfiguration(BasePluginConfiguration configuration)
{
- if (configuration == null)
- {
- throw new ArgumentNullException(nameof(configuration));
- }
+ ArgumentNullException.ThrowIfNull(configuration);
Configuration = (TConfigurationType)configuration;
diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
index 03882a0b9..e5ce0aa21 100644
--- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs
+++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
@@ -51,6 +51,14 @@ namespace MediaBrowser.Controller.Drawing
string GetImageBlurHash(string path);
/// <summary>
+ /// Gets the blurhash of the image.
+ /// </summary>
+ /// <param name="path">Path to the image file.</param>
+ /// <param name="imageDimensions">The image dimensions.</param>
+ /// <returns>BlurHash.</returns>
+ string GetImageBlurHash(string path, ImageDimensions imageDimensions);
+
+ /// <summary>
/// Gets the image cache tag.
/// </summary>
/// <param name="item">The item.</param>
diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs
index 77a857b78..e671e5c71 100644
--- a/MediaBrowser.Controller/Entities/AggregateFolder.cs
+++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs
@@ -171,10 +171,7 @@ namespace MediaBrowser.Controller.Entities
/// <exception cref="ArgumentNullException">Throws if child is null.</exception>
public void AddVirtualChild(BaseItem child)
{
- if (child == null)
- {
- throw new ArgumentNullException(nameof(child));
- }
+ ArgumentNullException.ThrowIfNull(child);
_virtualChildren.Add(child);
}
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
index 03d1f3304..bd397bdd1 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
@@ -169,8 +169,8 @@ namespace MediaBrowser.Controller.Entities.Audio
var childUpdateType = ItemUpdateType.None;
- // Refresh songs
- foreach (var item in items)
+ // Refresh songs only and not m3u files in album folder
+ foreach (var item in items.OfType<Audio>())
{
cancellationToken.ThrowIfCancellationRequested();
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
index 0f2d7e62d..15a79fa1f 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
@@ -8,9 +8,9 @@ using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
-using Diacritics.Extensions;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using Microsoft.Extensions.Logging;
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
index 73a25232e..7448d02ea 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
@@ -5,8 +5,8 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
-using Diacritics.Extensions;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities.Audio
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 2bb966d2c..41fce67fa 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -11,7 +11,6 @@ using System.Text;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
-using Diacritics.Extensions;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
@@ -1102,10 +1101,7 @@ namespace MediaBrowser.Controller.Entities
private MediaSourceInfo GetVersionInfo(bool enablePathSubstitution, BaseItem item, MediaSourceType type)
{
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
+ ArgumentNullException.ThrowIfNull(item);
var protocol = item.PathProtocol;
@@ -1545,10 +1541,7 @@ namespace MediaBrowser.Controller.Entities
/// <exception cref="ArgumentNullException">If user is null.</exception>
public bool IsParentalAllowed(User user)
{
- if (user == null)
- {
- throw new ArgumentNullException(nameof(user));
- }
+ ArgumentNullException.ThrowIfNull(user);
if (!IsVisibleViaTags(user))
{
@@ -1668,10 +1661,7 @@ namespace MediaBrowser.Controller.Entities
/// <exception cref="ArgumentNullException"><paramref name="user" /> is <c>null</c>.</exception>
public virtual bool IsVisible(User user)
{
- if (user == null)
- {
- throw new ArgumentNullException(nameof(user));
- }
+ ArgumentNullException.ThrowIfNull(user);
return IsParentalAllowed(user);
}
@@ -1843,10 +1833,7 @@ namespace MediaBrowser.Controller.Entities
DateTime? datePlayed,
bool resetPosition)
{
- if (user == null)
- {
- throw new ArgumentNullException(nameof(user));
- }
+ ArgumentNullException.ThrowIfNull(user);
var data = UserDataManager.GetUserData(user, this);
@@ -1877,10 +1864,7 @@ namespace MediaBrowser.Controller.Entities
/// <exception cref="ArgumentNullException">Throws if user is null.</exception>
public virtual void MarkUnplayed(User user)
{
- if (user == null)
- {
- throw new ArgumentNullException(nameof(user));
- }
+ ArgumentNullException.ThrowIfNull(user);
var data = UserDataManager.GetUserData(user, this);
@@ -2111,10 +2095,7 @@ namespace MediaBrowser.Controller.Entities
/// <returns>Image index.</returns>
public int GetImageIndex(ItemImageInfo image)
{
- if (image == null)
- {
- throw new ArgumentNullException(nameof(image));
- }
+ ArgumentNullException.ThrowIfNull(image);
if (image.Type == ImageType.Chapter)
{
@@ -2321,10 +2302,7 @@ namespace MediaBrowser.Controller.Entities
public virtual bool IsUnplayed(User user)
{
- if (user == null)
- {
- throw new ArgumentNullException(nameof(user));
- }
+ ArgumentNullException.ThrowIfNull(user);
var userdata = UserDataManager.GetUserData(user, this);
@@ -2617,7 +2595,8 @@ namespace MediaBrowser.Controller.Entities
return ExtraIds
.Select(LibraryManager.GetItemById)
.Where(i => i != null)
- .Where(i => i.ExtraType.HasValue && extraTypes.Contains(i.ExtraType.Value));
+ .Where(i => i.ExtraType.HasValue && extraTypes.Contains(i.ExtraType.Value))
+ .OrderBy(i => i.SortName);
}
public virtual long GetRunTimeTicksForPlayState()
diff --git a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
index e0583e630..948eb4375 100644
--- a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
+++ b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
@@ -71,15 +71,9 @@ namespace MediaBrowser.Controller.Entities
where T : BaseItem
where TU : BaseItem
{
- if (source == null)
- {
- throw new ArgumentNullException(nameof(source));
- }
+ ArgumentNullException.ThrowIfNull(source);
- if (dest == null)
- {
- throw new ArgumentNullException(nameof(dest));
- }
+ ArgumentNullException.ThrowIfNull(dest);
var destProps = typeof(TU).GetProperties().Where(x => x.CanWrite).ToList();
diff --git a/MediaBrowser.Controller/Entities/BasePluginFolder.cs b/MediaBrowser.Controller/Entities/BasePluginFolder.cs
index 272a37df1..afafaf1c2 100644
--- a/MediaBrowser.Controller/Entities/BasePluginFolder.cs
+++ b/MediaBrowser.Controller/Entities/BasePluginFolder.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System.Text.Json.Serialization;
@@ -13,7 +11,7 @@ namespace MediaBrowser.Controller.Entities
public abstract class BasePluginFolder : Folder, ICollectionFolder
{
[JsonIgnore]
- public virtual string CollectionType => null;
+ public virtual string? CollectionType => null;
[JsonIgnore]
public override bool SupportsInheritedParentImages => false;
diff --git a/MediaBrowser.Controller/Entities/Extensions.cs b/MediaBrowser.Controller/Entities/Extensions.cs
index 9ce8eebe3..1acb92fdc 100644
--- a/MediaBrowser.Controller/Entities/Extensions.cs
+++ b/MediaBrowser.Controller/Entities/Extensions.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using System.Linq;
using Jellyfin.Extensions;
@@ -19,9 +17,11 @@ namespace MediaBrowser.Controller.Entities
/// <param name="url">Trailer URL.</param>
public static void AddTrailerUrl(this BaseItem item, string url)
{
- if (string.IsNullOrEmpty(url))
+ ArgumentNullException.ThrowIfNull(url);
+
+ if (url.Length == 0)
{
- throw new ArgumentNullException(nameof(url));
+ throw new ArgumentException("String can't be empty", nameof(url));
}
var current = item.RemoteTrailers.FirstOrDefault(i => string.Equals(i.Url, url, StringComparison.OrdinalIgnoreCase));
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index b6983b73e..808f91810 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -860,7 +860,7 @@ namespace MediaBrowser.Controller.Entities
return true;
}
- if (!string.IsNullOrEmpty(query.AdjacentTo))
+ if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default))
{
Logger.LogDebug("Query requires post-filtering due to AdjacentTo");
return true;
@@ -892,29 +892,7 @@ namespace MediaBrowser.Controller.Entities
private static BaseItem[] SortItemsByRequest(InternalItemsQuery query, IReadOnlyList<BaseItem> items)
{
- var ids = query.ItemIds;
- int size = items.Count;
-
- // ids can potentially contain non-unique guids, but query result cannot,
- // so we include only first occurrence of each guid
- var positions = new Dictionary<Guid, int>(size);
- int index = 0;
- for (int i = 0; i < ids.Length; i++)
- {
- if (positions.TryAdd(ids[i], index))
- {
- index++;
- }
- }
-
- var newItems = new BaseItem[size];
- for (int i = 0; i < size; i++)
- {
- var item = items[i];
- newItems[positions[item.Id]] = item;
- }
-
- return newItems;
+ return items.OrderBy(i => Array.IndexOf(query.ItemIds, i.Id)).ToArray();
}
public QueryResult<BaseItem> GetItems(InternalItemsQuery query)
@@ -1029,9 +1007,9 @@ namespace MediaBrowser.Controller.Entities
#pragma warning restore CA1309
// This must be the last filter
- if (!string.IsNullOrEmpty(query.AdjacentTo))
+ if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default))
{
- items = UserViewBuilder.FilterForAdjacency(items.ToList(), query.AdjacentTo);
+ items = UserViewBuilder.FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
}
return UserViewBuilder.SortAndPage(items, null, query, LibraryManager, enableSorting);
@@ -1045,10 +1023,7 @@ namespace MediaBrowser.Controller.Entities
IServerConfigurationManager configurationManager,
ICollectionManager collectionManager)
{
- if (items == null)
- {
- throw new ArgumentNullException(nameof(items));
- }
+ ArgumentNullException.ThrowIfNull(items);
if (CollapseBoxSetItems(query, queryParent, user, configurationManager))
{
@@ -1295,20 +1270,14 @@ namespace MediaBrowser.Controller.Entities
public List<BaseItem> GetChildren(User user, bool includeLinkedChildren)
{
- if (user == null)
- {
- throw new ArgumentNullException(nameof(user));
- }
+ ArgumentNullException.ThrowIfNull(user);
return GetChildren(user, includeLinkedChildren, null);
}
public virtual List<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query)
{
- if (user == null)
- {
- throw new ArgumentNullException(nameof(user));
- }
+ ArgumentNullException.ThrowIfNull(user);
// the true root should return our users root folder children
if (IsPhysicalRoot)
@@ -1389,10 +1358,7 @@ namespace MediaBrowser.Controller.Entities
public virtual IEnumerable<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query)
{
- if (user == null)
- {
- throw new ArgumentNullException(nameof(user));
- }
+ ArgumentNullException.ThrowIfNull(user);
var result = new Dictionary<Guid, BaseItem>();
diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs
index 4be673237..ddf62dd4c 100644
--- a/MediaBrowser.Controller/Entities/Genre.cs
+++ b/MediaBrowser.Controller/Entities/Genre.cs
@@ -5,8 +5,8 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
-using Diacritics.Extensions;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities
diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
index db1697c79..13bfd07c3 100644
--- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
@@ -129,7 +129,7 @@ namespace MediaBrowser.Controller.Entities
public Guid[] ExcludeItemIds { get; set; }
- public string? AdjacentTo { get; set; }
+ public Guid? AdjacentTo { get; set; }
public string[] PersonTypes { get; set; }
diff --git a/MediaBrowser.Controller/Entities/PeopleHelper.cs b/MediaBrowser.Controller/Entities/PeopleHelper.cs
index 687ce1ec8..8571bfcea 100644
--- a/MediaBrowser.Controller/Entities/PeopleHelper.cs
+++ b/MediaBrowser.Controller/Entities/PeopleHelper.cs
@@ -11,10 +11,7 @@ namespace MediaBrowser.Controller.Entities
{
public static void AddPerson(List<PersonInfo> people, PersonInfo person)
{
- if (person == null)
- {
- throw new ArgumentNullException(nameof(person));
- }
+ ArgumentNullException.ThrowIfNull(person);
if (string.IsNullOrEmpty(person.Name))
{
diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs
index 045c1b89f..7f265084f 100644
--- a/MediaBrowser.Controller/Entities/Person.cs
+++ b/MediaBrowser.Controller/Entities/Person.cs
@@ -5,7 +5,7 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
-using Diacritics.Extensions;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Providers;
using Microsoft.Extensions.Logging;
diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs
index c8feb1c94..a3736a4bf 100644
--- a/MediaBrowser.Controller/Entities/Studio.cs
+++ b/MediaBrowser.Controller/Entities/Studio.cs
@@ -5,7 +5,7 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
-using Diacritics.Extensions;
+using Jellyfin.Extensions;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities
diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs
index bd8df2fac..599d35da6 100644
--- a/MediaBrowser.Controller/Entities/TV/Season.cs
+++ b/MediaBrowser.Controller/Entities/TV/Season.cs
@@ -244,7 +244,7 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary>
- /// <param name="replaceAllMetadata"><c>true</c> to replace metdata, <c>false</c> to not.</param>
+ /// <param name="replaceAllMetadata"><c>true</c> to replace metadata, <c>false</c> to not.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{
diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs
index a3c4a81fd..d66802a64 100644
--- a/MediaBrowser.Controller/Entities/TV/Series.cs
+++ b/MediaBrowser.Controller/Entities/TV/Series.cs
@@ -184,6 +184,11 @@ namespace MediaBrowser.Controller.Entities.TV
list.Insert(0, key);
}
+ if (this.TryGetProviderId(MetadataProvider.Custom, out key))
+ {
+ list.Insert(0, key);
+ }
+
return list;
}
@@ -261,7 +266,7 @@ namespace MediaBrowser.Controller.Entities.TV
DtoOptions = options
};
- if (!user.DisplayMissingEpisodes)
+ if (user == null || !user.DisplayMissingEpisodes)
{
query.IsMissing = false;
}
diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
index 2996104e7..f467a6038 100644
--- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs
+++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
@@ -433,9 +433,9 @@ namespace MediaBrowser.Controller.Entities
var user = query.User;
// This must be the last filter
- if (!string.IsNullOrEmpty(query.AdjacentTo))
+ if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default))
{
- items = FilterForAdjacency(items.ToList(), query.AdjacentTo);
+ items = FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
}
return SortAndPage(items, totalRecordLimit, query, libraryManager, true);
@@ -985,10 +985,9 @@ namespace MediaBrowser.Controller.Entities
return _userViewManager.GetUserSubView(parent.Id, type, localizationKey, sortName);
}
- public static IEnumerable<BaseItem> FilterForAdjacency(List<BaseItem> list, string adjacentToId)
+ public static IEnumerable<BaseItem> FilterForAdjacency(List<BaseItem> list, Guid adjacentTo)
{
- var adjacentToIdGuid = new Guid(adjacentToId);
- var adjacentToItem = list.FirstOrDefault(i => i.Id.Equals(adjacentToIdGuid));
+ var adjacentToItem = list.FirstOrDefault(i => i.Id.Equals(adjacentTo));
var index = list.IndexOf(adjacentToItem);
@@ -1005,7 +1004,7 @@ namespace MediaBrowser.Controller.Entities
nextId = list[index + 1].Id;
}
- return list.Where(i => i.Id.Equals(previousId) || i.Id.Equals(nextId) || i.Id.Equals(adjacentToIdGuid));
+ return list.Where(i => i.Id.Equals(previousId) || i.Id.Equals(nextId) || i.Id.Equals(adjacentTo));
}
}
}
diff --git a/MediaBrowser.Controller/IDisplayPreferencesManager.cs b/MediaBrowser.Controller/IDisplayPreferencesManager.cs
index 1678d5067..10c0f56e0 100644
--- a/MediaBrowser.Controller/IDisplayPreferencesManager.cs
+++ b/MediaBrowser.Controller/IDisplayPreferencesManager.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using System.Collections.Generic;
using Jellyfin.Data.Entities;
@@ -50,7 +48,7 @@ namespace MediaBrowser.Controller
/// <param name="itemId">The item id.</param>
/// <param name="client">The client string.</param>
/// <returns>The dictionary of custom item display preferences.</returns>
- Dictionary<string, string> ListCustomItemDisplayPreferences(Guid userId, Guid itemId, string client);
+ Dictionary<string, string?> ListCustomItemDisplayPreferences(Guid userId, Guid itemId, string client);
/// <summary>
/// Sets the custom item display preference for the user and client.
@@ -59,7 +57,7 @@ namespace MediaBrowser.Controller
/// <param name="itemId">The item id.</param>
/// <param name="client">The client id.</param>
/// <param name="customPreferences">A dictionary of custom item display preferences.</param>
- void SetCustomItemDisplayPreferences(Guid userId, Guid itemId, string client, Dictionary<string, string> customPreferences);
+ void SetCustomItemDisplayPreferences(Guid userId, Guid itemId, string client, Dictionary<string, string?> customPreferences);
/// <summary>
/// Saves changes made to the database.
diff --git a/MediaBrowser.Controller/IO/FileData.cs b/MediaBrowser.Controller/IO/FileData.cs
index 2429ac42d..71feae536 100644
--- a/MediaBrowser.Controller/IO/FileData.cs
+++ b/MediaBrowser.Controller/IO/FileData.cs
@@ -40,10 +40,7 @@ namespace MediaBrowser.Controller.IO
throw new ArgumentNullException(nameof(path));
}
- if (args == null)
- {
- throw new ArgumentNullException(nameof(args));
- }
+ ArgumentNullException.ThrowIfNull(args);
var entries = directoryService.GetFileSystemEntries(path);
diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs
index 75ec5f213..11afdc4ae 100644
--- a/MediaBrowser.Controller/IServerApplicationHost.cs
+++ b/MediaBrowser.Controller/IServerApplicationHost.cs
@@ -4,6 +4,7 @@
using System.Net;
using MediaBrowser.Common;
+using MediaBrowser.Common.Net;
using MediaBrowser.Model.System;
using Microsoft.AspNetCore.Http;
@@ -74,9 +75,10 @@ namespace MediaBrowser.Controller
/// <summary>
/// Gets an URL that can be used to access the API over LAN.
/// </summary>
+ /// <param name="hostname">An optional hostname to use.</param>
/// <param name="allowHttps">A value indicating whether to allow HTTPS.</param>
/// <returns>The API URL.</returns>
- string GetApiUrlForLocalAccess(bool allowHttps = true);
+ string GetApiUrlForLocalAccess(IPObject hostname = null, bool allowHttps = true);
/// <summary>
/// Gets a local (LAN) URL that can be used to access the API.
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index 313d27ce6..5905c25a5 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -570,5 +570,13 @@ namespace MediaBrowser.Controller.Library
Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason);
BaseItem GetParentItem(Guid? parentId, Guid? userId);
+
+ /// <summary>
+ /// Queue a library scan.
+ /// </summary>
+ /// <remarks>
+ /// This exists so plugins can trigger a library scan.
+ /// </remarks>
+ void QueueLibraryScan();
}
}
diff --git a/MediaBrowser.Controller/Library/NameExtensions.cs b/MediaBrowser.Controller/Library/NameExtensions.cs
index d2ed3465a..9d78b8b6c 100644
--- a/MediaBrowser.Controller/Library/NameExtensions.cs
+++ b/MediaBrowser.Controller/Library/NameExtensions.cs
@@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using Diacritics.Extensions;
+using Jellyfin.Extensions;
namespace MediaBrowser.Controller.Library
{
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index 6164e51cd..d4e025a43 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Controller</PackageId>
- <VersionPrefix>10.8.0</VersionPrefix>
+ <VersionPrefix>10.9.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
@@ -18,7 +18,6 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Diacritics" Version="3.3.10" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
@@ -57,7 +56,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
index 462585ce3..fb4e7bd1f 100644
--- a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
+++ b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
@@ -76,6 +76,12 @@ namespace MediaBrowser.Controller.MediaEncoding
public string Profile { get; set; }
/// <summary>
+ /// Gets or sets the video range type.
+ /// </summary>
+ /// <value>The video range type.</value>
+ public string VideoRangeType { get; set; }
+
+ /// <summary>
/// Gets or sets the level.
/// </summary>
/// <value>The level.</value>
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 633ba2d76..d0362b128 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -13,11 +13,13 @@ using System.Threading;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Extensions;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo;
+using Microsoft.Extensions.Configuration;
namespace MediaBrowser.Controller.MediaEncoding
{
@@ -32,6 +34,8 @@ namespace MediaBrowser.Controller.MediaEncoding
private readonly IApplicationPaths _appPaths;
private readonly IMediaEncoder _mediaEncoder;
private readonly ISubtitleEncoder _subtitleEncoder;
+ private readonly IConfiguration _config;
+ private readonly Version _minKernelVersioni915Hang = new Version(5, 18);
private static readonly string[] _videoProfilesH264 = new[]
{
@@ -54,11 +58,13 @@ namespace MediaBrowser.Controller.MediaEncoding
public EncodingHelper(
IApplicationPaths appPaths,
IMediaEncoder mediaEncoder,
- ISubtitleEncoder subtitleEncoder)
+ ISubtitleEncoder subtitleEncoder,
+ IConfiguration config)
{
_appPaths = appPaths;
_mediaEncoder = mediaEncoder;
_subtitleEncoder = subtitleEncoder;
+ _config = config;
}
public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
@@ -120,6 +126,7 @@ namespace MediaBrowser.Controller.MediaEncoding
&& _mediaEncoder.SupportsFilter("scale_vaapi")
&& _mediaEncoder.SupportsFilter("deinterlace_vaapi")
&& _mediaEncoder.SupportsFilter("tonemap_vaapi")
+ && _mediaEncoder.SupportsFilter("procamp_vaapi")
&& _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVaapiFrameSync)
&& _mediaEncoder.SupportsFilter("hwupload_vaapi");
}
@@ -144,35 +151,50 @@ namespace MediaBrowser.Controller.MediaEncoding
private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
{
- if (state.VideoStream == null)
+ if (state.VideoStream == null
+ || !options.EnableTonemapping
+ || GetVideoColorBitDepth(state) != 10)
{
return false;
}
- return options.EnableTonemapping
- && (string.Equals(state.VideoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
- || string.Equals(state.VideoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
- && GetVideoColorBitDepth(state) == 10;
+ if (string.Equals(state.VideoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
+ && string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
+ && string.Equals(state.VideoStream.VideoRangeType, "DOVI", StringComparison.OrdinalIgnoreCase))
+ {
+ // Only native SW decoder and HW accelerator can parse dovi rpu.
+ var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
+ var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
+ var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
+ var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
+ var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
+ return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder;
+ }
+
+ return string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
+ && (string.Equals(state.VideoStream.VideoRangeType, "HDR10", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(state.VideoStream.VideoRangeType, "HLG", StringComparison.OrdinalIgnoreCase));
}
private bool IsVaapiVppTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
{
- if (state.VideoStream == null)
+ if (state.VideoStream == null
+ || !options.EnableVppTonemapping
+ || GetVideoColorBitDepth(state) != 10)
{
return false;
}
// Native VPP tonemapping may come to QSV in the future.
- return options.EnableVppTonemapping
- && string.Equals(state.VideoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
- && GetVideoColorBitDepth(state) == 10;
+ return string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
+ && string.Equals(state.VideoStream.VideoRangeType, "HDR10", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Gets the name of the output video codec.
/// </summary>
- /// <param name="state">Encording state.</param>
+ /// <param name="state">Encoding state.</param>
/// <param name="encodingOptions">Encoding options.</param>
/// <returns>Encoder string.</returns>
public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
@@ -234,6 +256,21 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Empty;
}
+ /// <summary>
+ /// Gets the referer param.
+ /// </summary>
+ /// <param name="state">The state.</param>
+ /// <returns>System.String.</returns>
+ public string GetRefererParam(EncodingJobInfo state)
+ {
+ if (state.RemoteHttpHeaders.TryGetValue("Referer", out string referer))
+ {
+ return "-referer \"" + referer + "\"";
+ }
+
+ return string.Empty;
+ }
+
public static string GetInputFormat(string container)
{
if (string.IsNullOrEmpty(container))
@@ -516,8 +553,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(codec, "flac", StringComparison.OrdinalIgnoreCase))
{
- // flac is experimental in mp4 muxer
- return "flac -strict -2";
+ return "flac";
}
return codec.ToLowerInvariant();
@@ -696,6 +732,9 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (_mediaEncoder.IsVaapiDeviceInteli965)
{
+ // Only override i965 since it has lower priority than iHD in libva lookup.
+ Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME", "i965");
+ Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME_JELLYFIN", "i965");
args.Append(GetVaapiDeviceArgs(null, "i965", null, VaapiAlias));
}
else
@@ -907,6 +946,13 @@ namespace MediaBrowser.Controller.MediaEncoding
arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"');
}
+ // Disable auto inserted SW scaler for HW decoders in case of changed resolution.
+ var isSwDecoder = string.IsNullOrEmpty(GetHardwareVideoDecoder(state, options));
+ if (!isSwDecoder)
+ {
+ arg.Append(" -autoscale 0");
+ }
+
return arg.ToString();
}
@@ -1024,7 +1070,8 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase))
{
- return FormattableString.Invariant($" -qmin 18 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
+ // Override the too high default qmin 18 in transcoding preset
+ return FormattableString.Invariant($" -rc cbr -qmin 0 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
}
if (string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
@@ -1062,10 +1109,12 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
{
- // Clients may direct play higher than level 41, but there's no reason to transcode higher.
- if (requestLevel >= 41)
+ // Transcode to level 5.1 and lower for maximum compatibility.
+ // h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4.
+ // https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
+ if (requestLevel >= 51)
{
- return "41";
+ return "51";
}
}
}
@@ -1118,16 +1167,15 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.SubtitleStream.IsExternal)
{
- var subtitlePath = state.SubtitleStream.Path;
var charsetParam = string.Empty;
if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
{
var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(
- subtitlePath,
- state.SubtitleStream.Language,
- state.MediaSource.Protocol,
- CancellationToken.None).GetAwaiter().GetResult();
+ state.SubtitleStream,
+ state.SubtitleStream.Language,
+ state.MediaSource,
+ CancellationToken.None).GetAwaiter().GetResult();
if (!string.IsNullOrEmpty(charenc))
{
@@ -1139,7 +1187,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(
CultureInfo.InvariantCulture,
"subtitles=f='{0}'{1}{2}{3}{4}{5}",
- _mediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
+ _mediaEncoder.EscapeSubtitleFilterPath(state.SubtitleStream.Path),
charsetParam,
alphaParam,
sub2videoParam,
@@ -1220,10 +1268,9 @@ namespace MediaBrowser.Controller.MediaEncoding
// Example: we encoded half of desired length, then codec detected
// scene cut and inserted a keyframe; next forced keyframe would
// be created outside of segment, which breaks seeking.
- // -sc_threshold 0 is used to prevent the hardware encoder from post processing to break the set keyframe.
gopArg = string.Format(
CultureInfo.InvariantCulture,
- " -g:v:0 {0} -keyint_min:v:0 {0} -sc_threshold:v:0 0",
+ " -g:v:0 {0} -keyint_min:v:0 {0}",
Math.Ceiling(segmentLength * framerate.Value));
}
@@ -1243,6 +1290,12 @@ namespace MediaBrowser.Controller.MediaEncoding
|| string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
{
args += keyFrameArg;
+
+ // prevent the libx264 from post processing to break the set keyframe.
+ if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase))
+ {
+ args += " -sc_threshold:v:0 0";
+ }
}
else
{
@@ -1271,6 +1324,10 @@ namespace MediaBrowser.Controller.MediaEncoding
// which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping.
var intelLowPowerHwEncoding = false;
+ // Workaround for linux 5.18+ i915 hang at cost of performance.
+ // https://github.com/intel/media-driver/issues/1456
+ var enableWaFori915Hang = false;
+
if (string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
{
var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965;
@@ -1286,6 +1343,20 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
{
+ if (OperatingSystem.IsLinux() && Environment.OSVersion.Version >= _minKernelVersioni915Hang)
+ {
+ var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty;
+ var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)
+ || vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
+ var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
+ && IsVaapiSupported(state)
+ && IsOpenclFullSupported()
+ && !IsVaapiVppTonemapAvailable(state, encodingOptions)
+ && IsHwTonemapAvailable(state, encodingOptions);
+
+ enableWaFori915Hang = isIntelDecoder && doOclTonemap;
+ }
+
if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder;
@@ -1294,6 +1365,10 @@ namespace MediaBrowser.Controller.MediaEncoding
{
intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder;
}
+ else
+ {
+ enableWaFori915Hang = false;
+ }
}
if (intelLowPowerHwEncoding)
@@ -1301,6 +1376,11 @@ namespace MediaBrowser.Controller.MediaEncoding
param += " -low_power 1";
}
+ if (enableWaFori915Hang)
+ {
+ param += " -async_depth 1";
+ }
+
var isVc1 = string.Equals(state.VideoStream?.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
@@ -1682,6 +1762,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// Can't stream copy if we're burning in subtitles
if (request.SubtitleStreamIndex.HasValue
+ && request.SubtitleStreamIndex.Value >= 0
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
{
return false;
@@ -1728,6 +1809,20 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
+ var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec);
+ if (requestedRangeTypes.Length > 0)
+ {
+ if (string.IsNullOrEmpty(videoStream.VideoRangeType))
+ {
+ return false;
+ }
+
+ if (!requestedRangeTypes.Contains(videoStream.VideoRangeType, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+ }
+
// Video width must fall within requested value
if (request.MaxWidth.HasValue
&& (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value))
@@ -1868,7 +1963,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return request.EnableAutoStreamCopy;
}
- public int? GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideoCodec)
+ public int GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideoCodec)
{
var bitrate = request.VideoBitRate;
@@ -1900,7 +1995,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
- return bitrate;
+ // Cap the max target bitrate to intMax/2 to satisfy the bufsize=bitrate*2.
+ return Math.Min(bitrate ?? 0, int.MaxValue / 2);
}
private int GetMinBitrate(int sourceBitrate, int requestedBitrate)
@@ -1980,6 +2076,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
{
@@ -2213,13 +2311,13 @@ namespace MediaBrowser.Controller.MediaEncoding
return state.IsInputVideo ? "-sn" : string.Empty;
}
- // We have media info, but we don't know the stream indexes
+ // We have media info, but we don't know the stream index
if (state.VideoStream != null && state.VideoStream.Index == -1)
{
return "-sn";
}
- // We have media info, but we don't know the stream indexes
+ // We have media info, but we don't know the stream index
if (state.AudioStream != null && state.AudioStream.Index == -1)
{
return state.IsInputVideo ? "-sn" : string.Empty;
@@ -2229,10 +2327,12 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.VideoStream != null)
{
+ int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
+
args += string.Format(
CultureInfo.InvariantCulture,
"-map 0:{0}",
- state.VideoStream.Index);
+ videoStreamIndex);
}
else
{
@@ -2242,23 +2342,27 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.AudioStream != null)
{
+ int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
if (state.AudioStream.IsExternal)
{
- int externalAudioMapIndex = state.SubtitleStream != null && state.SubtitleStream.IsExternal ? 2 : 1;
- int externalAudioStream = state.MediaSource.MediaStreams.Where(i => i.Path == state.AudioStream.Path).ToList().IndexOf(state.AudioStream);
+ bool hasExternalGraphicsSubs = state.SubtitleStream != null
+ && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
+ && state.SubtitleStream.IsExternal
+ && !state.SubtitleStream.IsTextSubtitleStream;
+ int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
args += string.Format(
CultureInfo.InvariantCulture,
" -map {0}:{1}",
externalAudioMapIndex,
- externalAudioStream);
+ audioStreamIndex);
}
else
{
args += string.Format(
CultureInfo.InvariantCulture,
" -map 0:{0}",
- state.AudioStream.Index);
+ audioStreamIndex);
}
}
else
@@ -2273,14 +2377,21 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
{
+ int subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
+
args += string.Format(
CultureInfo.InvariantCulture,
" -map 0:{0}",
- state.SubtitleStream.Index);
+ subtitleStreamIndex);
}
else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
{
- args += " -map 1:0 -sn";
+ int externalSubtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
+
+ args += string.Format(
+ CultureInfo.InvariantCulture,
+ " -map 1:{0} -sn",
+ externalSubtitleStreamIndex);
}
return args;
@@ -2509,7 +2620,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(
CultureInfo.InvariantCulture,
- "scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/{2})*{2}:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2",
+ "scale=trunc(min(max(iw\\,ih*a)\\,min({0}\\,{1}*a))/{2})*{2}:trunc(min(max(iw/a\\,ih)\\,min({0}/a\\,{1}))/2)*2",
maxWidthParam,
maxHeightParam,
scaleVal);
@@ -2553,7 +2664,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(
CultureInfo.InvariantCulture,
- "scale=trunc(min(max(iw\\,ih*dar)\\,{0})/{1})*{1}:trunc(ow/dar/2)*2",
+ "scale=trunc(min(max(iw\\,ih*a)\\,{0})/{1})*{1}:trunc(ow/a/2)*2",
maxWidthParam,
scaleVal);
}
@@ -2565,7 +2676,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(
CultureInfo.InvariantCulture,
- "scale=trunc(oh*a/{1})*{1}:min(max(iw/dar\\,ih)\\,{0})",
+ "scale=trunc(oh*a/{1})*{1}:min(max(iw/a\\,ih)\\,{0})",
maxHeightParam,
scaleVal);
}
@@ -2614,7 +2725,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else
{
- filter = "scale={0}:trunc({0}/dar/2)*2";
+ filter = "scale={0}:trunc({0}/a/2)*2";
}
}
@@ -2665,7 +2776,18 @@ namespace MediaBrowser.Controller.MediaEncoding
var args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709";
- if (!hwTonemapSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
+ if (hwTonemapSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ args += ",procamp_vaapi=b={2}:c={3}:extra_hw_frames=16";
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ args,
+ hwTonemapSuffix,
+ videoFormat ?? "nv12",
+ options.VppTonemappingBrightness,
+ options.VppTonemappingContrast);
+ }
+ else
{
args += ":tonemap={2}:peak={3}:desat={4}";
@@ -2768,8 +2890,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (hasGraphicalSubs)
{
- // [0:s]scale=s=1280x720
- var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ // [0:s]scale=expr
+ var subSwScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -2867,7 +2989,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// sw => hw
if (doCuTonemap)
{
- mainFilters.Add("hwupload");
+ mainFilters.Add("hwupload=derive_device=cuda");
}
}
@@ -2947,7 +3069,7 @@ namespace MediaBrowser.Controller.MediaEncoding
subFilters.Add(subTextSubtitlesFilter);
}
- subFilters.Add("hwupload");
+ subFilters.Add("hwupload=derive_device=cuda");
overlayFilters.Add("overlay_cuda=eof_action=endall:shortest=1:repeatlast=0");
}
}
@@ -2955,7 +3077,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var subSwScaleFilter = isSwDecoder
+ ? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
+ : GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -3057,7 +3181,9 @@ namespace MediaBrowser.Controller.MediaEncoding
// sw => hw
if (doOclTonemap)
{
- mainFilters.Add("hwupload");
+ mainFilters.Add("hwupload=derive_device=d3d11va:extra_hw_frames=16");
+ mainFilters.Add("format=d3d11");
+ mainFilters.Add("hwmap=derive_device=opencl");
}
}
@@ -3084,7 +3210,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var memoryOutput = false;
var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
- if ((isD3d11vaDecoder && isSwEncoder) || isUploadForOclTonemap)
+ if (isD3d11vaDecoder && isSwEncoder)
{
memoryOutput = true;
@@ -3096,7 +3222,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// OUTPUT yuv420p surface
- if (isSwDecoder && isAmfEncoder)
+ if (isSwDecoder && isAmfEncoder && !isUploadForOclTonemap)
{
memoryOutput = true;
}
@@ -3111,7 +3237,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
- if (isDxInDxOut && !hasSubs)
+ if ((isDxInDxOut || isUploadForOclTonemap) && !hasSubs)
{
// OUTPUT d3d11(nv12) surface(vram)
// reverse-mapping via d3d11-opencl interop.
@@ -3122,7 +3248,7 @@ namespace MediaBrowser.Controller.MediaEncoding
/* Make sub and overlay filters for subtitle stream */
var subFilters = new List<string>();
var overlayFilters = new List<string>();
- if (isDxInDxOut)
+ if (isDxInDxOut || isUploadForOclTonemap)
{
if (hasSubs)
{
@@ -3143,7 +3269,7 @@ namespace MediaBrowser.Controller.MediaEncoding
subFilters.Add(subTextSubtitlesFilter);
}
- subFilters.Add("hwupload");
+ subFilters.Add("hwupload=derive_device=opencl");
overlayFilters.Add("overlay_opencl=eof_action=endall:shortest=1:repeatlast=0");
overlayFilters.Add("hwmap=derive_device=d3d11va:reverse=1");
overlayFilters.Add("format=d3d11");
@@ -3153,7 +3279,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var subSwScaleFilter = isSwDecoder
+ ? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
+ : GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -3275,7 +3403,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// sw => hw
if (doOclTonemap)
{
- mainFilters.Add("hwupload");
+ mainFilters.Add("hwupload=derive_device=opencl");
}
}
else if (isD3d11vaDecoder || isQsvDecoder)
@@ -3381,7 +3509,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// qsv requires a fixed pool size.
- subFilters.Add("hwupload=extra_hw_frames=32");
+ // default to 64 otherwise it will fail on certain iGPU.
+ subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var overlaySize = (overlayW.HasValue && overlayH.HasValue)
@@ -3398,7 +3527,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var subSwScaleFilter = isSwDecoder
+ ? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
+ : GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -3469,7 +3600,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// sw => hw
if (doOclTonemap)
{
- mainFilters.Add("hwupload");
+ mainFilters.Add("hwupload=derive_device=opencl");
}
}
else if (isVaapiDecoder || isQsvDecoder)
@@ -3589,7 +3720,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// qsv requires a fixed pool size.
- subFilters.Add("hwupload=extra_hw_frames=32");
+ // default to 64 otherwise it will fail on certain iGPU.
+ subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var overlaySize = (overlayW.HasValue && overlayH.HasValue)
@@ -3606,7 +3738,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var subSwScaleFilter = isSwDecoder
+ ? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
+ : GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
}
@@ -3650,7 +3784,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var newfilters = new List<string>();
var noOverlay = swFilterChain.OverlayFilters.Count == 0;
newfilters.AddRange(noOverlay ? swFilterChain.MainFilters : swFilterChain.OverlayFilters);
- newfilters.Add("hwupload");
+ newfilters.Add("hwupload=derive_device=vaapi");
var mainFilters = noOverlay ? newfilters : swFilterChain.MainFilters;
var overlayFilters = noOverlay ? swFilterChain.OverlayFilters : newfilters;
@@ -3731,7 +3865,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// sw => hw
if (doOclTonemap)
{
- mainFilters.Add("hwupload");
+ mainFilters.Add("hwupload=derive_device=opencl");
}
}
else if (isVaapiDecoder)
@@ -3836,7 +3970,7 @@ namespace MediaBrowser.Controller.MediaEncoding
subFilters.Add(subTextSubtitlesFilter);
}
- subFilters.Add("hwupload");
+ subFilters.Add("hwupload=derive_device=vaapi");
var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var overlaySize = (overlayW.HasValue && overlayH.HasValue)
@@ -3853,7 +3987,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var subSwScaleFilter = isSwDecoder
+ ? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
+ : GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
@@ -3925,7 +4061,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// sw => hw
if (doOclTonemap)
{
- mainFilters.Add("hwupload");
+ mainFilters.Add("hwupload=derive_device=opencl");
}
}
else if (isVaapiDecoder)
@@ -3955,7 +4091,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
mainFilters.Add("hwdownload");
mainFilters.Add("format=p010le");
- mainFilters.Add("hwupload");
+ mainFilters.Add("hwupload=derive_device=opencl");
}
}
@@ -4028,7 +4164,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var subSwScaleFilter = isSwDecoder
+ ? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
+ : GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
@@ -4124,9 +4262,8 @@ namespace MediaBrowser.Controller.MediaEncoding
string.Join(',', overlayFilters));
var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
- var subtitleStreamIndex = state.SubtitleStream.IsExternal
- ? 0
- : state.SubtitleStream.Index;
+ var subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
+ var videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
if (hasSubs)
{
@@ -4147,7 +4284,7 @@ namespace MediaBrowser.Controller.MediaEncoding
filterStr,
mapPrefix,
subtitleStreamIndex,
- state.VideoStream.Index,
+ videoStreamIndex,
mainStr,
subStr,
overlayStr);
@@ -4205,6 +4342,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return videoStream.BitDepth.Value;
}
else if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
{
return 8;
@@ -4237,14 +4375,18 @@ namespace MediaBrowser.Controller.MediaEncoding
protected string GetHardwareVideoDecoder(EncodingJobInfo state, EncodingOptions options)
{
var videoStream = state.VideoStream;
- if (videoStream == null)
+ var mediaSource = state.MediaSource;
+ if (videoStream == null || mediaSource == null)
{
return null;
}
- // Only use alternative encoders for video files.
- var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile;
- if (videoType != VideoType.VideoFile)
+ // HWA decoders can handle both video files and video folders.
+ var videoType = mediaSource.VideoType;
+ if (videoType != VideoType.VideoFile
+ && videoType != VideoType.Iso
+ && videoType != VideoType.Dvd
+ && videoType != VideoType.BluRay)
{
return null;
}
@@ -4413,7 +4555,9 @@ namespace MediaBrowser.Controller.MediaEncoding
if (isD3d11Supported && isCodecAvailable)
{
- return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
+ // set -threads 3 to intel d3d11va decoder explicitly. Lower threads may result in dead lock.
+ // on newer devices such as Xe, the larger the init_pool_size, the longer the initialization time for opencl to derive from d3d11.
+ return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty) + " -threads 3" + (isAv1 ? " -c:v av1" : string.Empty);
}
}
else
@@ -4491,7 +4635,8 @@ namespace MediaBrowser.Controller.MediaEncoding
var hwSurface = (isIntelDx11OclSupported || isIntelVaapiOclSupported)
&& _mediaEncoder.SupportsFilter("alphasrc");
- var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
// TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool
@@ -4550,7 +4695,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
var hwSurface = IsCudaFullSupported() && _mediaEncoder.SupportsFilter("alphasrc");
- var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
// TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool
@@ -4616,7 +4762,8 @@ namespace MediaBrowser.Controller.MediaEncoding
var hwSurface = _mediaEncoder.SupportsHwaccel("d3d11va")
&& IsOpenclFullSupported()
&& _mediaEncoder.SupportsFilter("alphasrc");
- var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsAmf = is8bitSwFormatsAmf || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
if (is8bitSwFormatsAmf)
@@ -4636,11 +4783,6 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
}
-
- if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
- {
- return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface);
- }
}
if (is8_10bitSwFormatsAmf)
@@ -4677,7 +4819,8 @@ namespace MediaBrowser.Controller.MediaEncoding
&& IsVaapiFullSupported()
&& IsOpenclFullSupported()
&& _mediaEncoder.SupportsFilter("alphasrc");
- var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
if (is8bitSwFormatsVaapi)
@@ -4734,7 +4877,8 @@ namespace MediaBrowser.Controller.MediaEncoding
return null;
}
- var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
if (is8bitSwFormatsVt)
@@ -4840,21 +4984,20 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions, string segmentContainer)
{
var inputModifier = string.Empty;
- var probeSizeArgument = string.Empty;
+ var analyzeDurationArgument = string.Empty;
+
+ // Apply -analyzeduration as per the environment variable,
+ // otherwise ffmpeg will break on certain files due to default value is 0.
+ // The default value of -probesize is more than enough, so leave it as is.
+ var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
- string analyzeDurationArgument;
- if (state.MediaSource.AnalyzeDurationMs.HasValue)
+ if (state.MediaSource.AnalyzeDurationMs > 0)
{
analyzeDurationArgument = "-analyzeduration " + (state.MediaSource.AnalyzeDurationMs.Value * 1000).ToString(CultureInfo.InvariantCulture);
}
- else
- {
- analyzeDurationArgument = string.Empty;
- }
-
- if (!string.IsNullOrEmpty(probeSizeArgument))
+ else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
{
- inputModifier += " " + probeSizeArgument;
+ analyzeDurationArgument = "-analyzeduration " + ffmpegAnalyzeDuration;
}
if (!string.IsNullOrEmpty(analyzeDurationArgument))
@@ -4873,12 +5016,21 @@ namespace MediaBrowser.Controller.MediaEncoding
inputModifier = inputModifier.Trim();
+ var refererParam = GetRefererParam(state);
+
+ if (!string.IsNullOrEmpty(refererParam))
+ {
+ inputModifier += " " + refererParam;
+ }
+
+ inputModifier = inputModifier.Trim();
+
inputModifier += " " + GetFastSeekCommandLineParameter(state, encodingOptions, segmentContainer);
inputModifier = inputModifier.Trim();
if (state.InputProtocol == MediaProtocol.Rtsp)
{
- inputModifier += " -rtsp_transport tcp -rtsp_transport udp -rtsp_flags prefer_tcp";
+ inputModifier += " -rtsp_transport tcp+udp -rtsp_flags prefer_tcp";
}
if (!string.IsNullOrEmpty(state.InputAudioSync))
@@ -4953,15 +5105,9 @@ namespace MediaBrowser.Controller.MediaEncoding
MediaSourceInfo mediaSource,
string requestedUrl)
{
- if (state == null)
- {
- throw new ArgumentNullException(nameof(state));
- }
+ ArgumentNullException.ThrowIfNull(state);
- if (mediaSource == null)
- {
- throw new ArgumentNullException(nameof(mediaSource));
- }
+ ArgumentNullException.ThrowIfNull(mediaSource);
var path = mediaSource.Path;
var protocol = mediaSource.Protocol;
@@ -5357,12 +5503,22 @@ namespace MediaBrowser.Controller.MediaEncoding
audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
}
- // opus will fail on 44100
if (!string.Equals(state.OutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase))
{
- if (state.OutputAudioSampleRate.HasValue)
+ // opus only supports specific sampling rates
+ var sampleRate = state.OutputAudioSampleRate;
+ if (sampleRate.HasValue)
{
- audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture));
+ var sampleRateValue = sampleRate.Value switch
+ {
+ <= 8000 => 8000,
+ <= 12000 => 12000,
+ <= 16000 => 16000,
+ <= 24000 => 24000,
+ _ => 48000
+ };
+
+ audioTranscodeParams.Add("-ar " + sampleRateValue.ToString(CultureInfo.InvariantCulture));
}
}
@@ -5384,6 +5540,28 @@ namespace MediaBrowser.Controller.MediaEncoding
string.Empty).Trim();
}
+ public static int FindIndex(IReadOnlyList<MediaStream> mediaStreams, MediaStream streamToFind)
+ {
+ var index = 0;
+ var length = mediaStreams.Count;
+
+ for (var i = 0; i < length; i++)
+ {
+ var currentMediaStream = mediaStreams[i];
+ if (currentMediaStream == streamToFind)
+ {
+ return index;
+ }
+
+ if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
+ {
+ index++;
+ }
+ }
+
+ return -1;
+ }
+
public static bool IsCopyCodec(string codec)
{
return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
index 0824590f2..c9625cf1d 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
@@ -6,7 +6,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using System.Text.Json.Serialization;
using Jellyfin.Data.Entities;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
@@ -366,6 +365,28 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
+ /// <summary>
+ /// Gets the target video range type.
+ /// </summary>
+ public string TargetVideoRangeType
+ {
+ get
+ {
+ if (BaseRequest.Static || EncodingHelper.IsCopyCodec(OutputVideoCodec))
+ {
+ return VideoStream?.VideoRangeType;
+ }
+
+ var requestedRangeType = GetRequestedRangeTypes(ActualOutputVideoCodec).FirstOrDefault();
+ if (!string.IsNullOrEmpty(requestedRangeType))
+ {
+ return requestedRangeType;
+ }
+
+ return null;
+ }
+ }
+
public string TargetVideoCodecTag
{
get
@@ -579,6 +600,26 @@ namespace MediaBrowser.Controller.MediaEncoding
return Array.Empty<string>();
}
+ public string[] GetRequestedRangeTypes(string codec)
+ {
+ if (!string.IsNullOrEmpty(BaseRequest.VideoRangeType))
+ {
+ return BaseRequest.VideoRangeType.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
+ }
+
+ if (!string.IsNullOrEmpty(codec))
+ {
+ var rangetype = BaseRequest.GetOption(codec, "rangetype");
+
+ if (!string.IsNullOrEmpty(rangetype))
+ {
+ return rangetype.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
+ }
+ }
+
+ return Array.Empty<string>();
+ }
+
public string GetRequestedLevel(string codec)
{
if (!string.IsNullOrEmpty(BaseRequest.Level))
diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
index 6bf3e7b46..69d0bf45c 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -38,6 +38,12 @@ namespace MediaBrowser.Controller.MediaEncoding
Version EncoderVersion { get; }
/// <summary>
+ /// Whether p key pausing is supported.
+ /// </summary>
+ /// <value><c>true</c> if p key pausing is supported, <c>false</c> otherwise.</value>
+ bool IsPkeyPauseSupported { get; }
+
+ /// <summary>
/// Gets a value indicating whether the configured Vaapi device is from AMD(radeonsi/r600 Mesa driver).
/// </summary>
/// <value><c>true</c> if the Vaapi device is an AMD(radeonsi/r600 Mesa driver) GPU, <c>false</c> otherwise.</value>
@@ -142,6 +148,13 @@ namespace MediaBrowser.Controller.MediaEncoding
string GetInputArgument(string inputFile, MediaSourceInfo mediaSource);
/// <summary>
+ /// Gets the input argument for an external subtitle file.
+ /// </summary>
+ /// <param name="inputFile">The input file.</param>
+ /// <returns>System.String.</returns>
+ string GetExternalSubtitleInputArgument(string inputFile);
+
+ /// <summary>
/// Gets the time parameter.
/// </summary>
/// <param name="ticks">The ticks.</param>
diff --git a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs
index 4483cf708..5bf83a9e3 100644
--- a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs
@@ -6,7 +6,8 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.MediaEncoding
{
@@ -37,11 +38,11 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets the subtitle language encoding parameter.
/// </summary>
- /// <param name="path">The path.</param>
+ /// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="language">The language.</param>
- /// <param name="protocol">The protocol.</param>
+ /// <param name="mediaSource">The media source.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns>
- Task<string> GetSubtitleFileCharacterSet(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken);
+ Task<string> GetSubtitleFileCharacterSet(MediaStream subtitleStream, string language, MediaSourceInfo mediaSource, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
index 8b2837ee3..d8475f12a 100644
--- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
+++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
@@ -111,7 +111,7 @@ namespace MediaBrowser.Controller.MediaEncoding
percent = 100.0 * currentMs / totalMs;
- transcodingPosition = val;
+ transcodingPosition = TimeSpan.FromMilliseconds(currentMs);
}
}
else if (part.StartsWith("size=", StringComparison.OrdinalIgnoreCase))
diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
index eadc09fd4..647de5003 100644
--- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
+++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
@@ -38,10 +38,7 @@ namespace MediaBrowser.Controller.Net
protected BasePeriodicWebSocketListener(ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> logger)
{
- if (logger == null)
- {
- throw new ArgumentNullException(nameof(logger));
- }
+ ArgumentNullException.ThrowIfNull(logger);
Logger = logger;
}
@@ -77,10 +74,7 @@ namespace MediaBrowser.Controller.Net
/// <returns>Task.</returns>
public Task ProcessMessageAsync(WebSocketMessageInfo message)
{
- if (message == null)
- {
- throw new ArgumentNullException(nameof(message));
- }
+ ArgumentNullException.ThrowIfNull(message);
if (message.MessageType == StartType)
{
diff --git a/MediaBrowser.Controller/Net/ISessionContext.cs b/MediaBrowser.Controller/Net/ISessionContext.cs
deleted file mode 100644
index b48181b3f..000000000
--- a/MediaBrowser.Controller/Net/ISessionContext.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Threading.Tasks;
-using Jellyfin.Data.Entities;
-using MediaBrowser.Controller.Session;
-using Microsoft.AspNetCore.Http;
-
-namespace MediaBrowser.Controller.Net
-{
- public interface ISessionContext
- {
- Task<SessionInfo> GetSession(object requestContext);
-
- Task<User?> GetUser(object requestContext);
-
- Task<SessionInfo> GetSession(HttpContext requestContext);
-
- Task<User?> GetUser(HttpContext requestContext);
- }
-}
diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs
index 2c6483ae2..4f2492b89 100644
--- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs
+++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs
@@ -6,11 +6,10 @@ using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Net;
-using Microsoft.AspNetCore.Http;
namespace MediaBrowser.Controller.Net
{
- public interface IWebSocketConnection
+ public interface IWebSocketConnection : IAsyncDisposable, IDisposable
{
/// <summary>
/// Occurs when [closed].
diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs
index 828ecb2c5..7ae9ff746 100644
--- a/MediaBrowser.Controller/Playlists/Playlist.cs
+++ b/MediaBrowser.Controller/Playlists/Playlist.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json.Serialization;
diff --git a/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs b/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs
index a9d16a49e..fd73ed5f8 100644
--- a/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs
+++ b/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs
@@ -34,8 +34,8 @@ namespace MediaBrowser.Controller.Providers
public bool IsReplacingImage(ImageType type)
{
- return ImageRefreshMode == MetadataRefreshMode.FullRefresh &&
- (ReplaceAllImages || ReplaceImages.Contains(type));
+ return ImageRefreshMode == MetadataRefreshMode.FullRefresh
+ && (ReplaceAllImages || ReplaceImages.Contains(type));
}
}
}
diff --git a/MediaBrowser.Controller/Resolvers/ResolverPriority.cs b/MediaBrowser.Controller/Resolvers/ResolverPriority.cs
index d4f975b6d..d0810c639 100644
--- a/MediaBrowser.Controller/Resolvers/ResolverPriority.cs
+++ b/MediaBrowser.Controller/Resolvers/ResolverPriority.cs
@@ -6,6 +6,11 @@ namespace MediaBrowser.Controller.Resolvers
public enum ResolverPriority
{
/// <summary>
+ /// The highest priority. Used by plugins to bypass the default server resolvers.
+ /// </summary>
+ Plugin = 0,
+
+ /// <summary>
/// The first.
/// </summary>
First = 1,
diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs
index c86556095..b16399598 100644
--- a/MediaBrowser.Controller/Session/ISessionManager.cs
+++ b/MediaBrowser.Controller/Session/ISessionManager.cs
@@ -352,6 +352,6 @@ namespace MediaBrowser.Controller.Session
/// <returns>Task.</returns>
Task RevokeUserTokens(Guid userId, string currentAccessToken);
- void CloseIfNeeded(SessionInfo session);
+ Task CloseIfNeededAsync(SessionInfo session);
}
}
diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs
index c2ca23386..b4520ae48 100644
--- a/MediaBrowser.Controller/Session/SessionInfo.cs
+++ b/MediaBrowser.Controller/Session/SessionInfo.cs
@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
+using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Session;
@@ -17,7 +18,7 @@ namespace MediaBrowser.Controller.Session
/// <summary>
/// Class SessionInfo.
/// </summary>
- public sealed class SessionInfo : IDisposable
+ public sealed class SessionInfo : IAsyncDisposable, IDisposable
{
// 1 second
private const long ProgressIncrement = 10000000;
@@ -380,10 +381,28 @@ namespace MediaBrowser.Controller.Session
{
if (controller is IDisposable disposable)
{
- _logger.LogDebug("Disposing session controller {0}", disposable.GetType().Name);
+ _logger.LogDebug("Disposing session controller synchronously {TypeName}", disposable.GetType().Name);
disposable.Dispose();
}
}
}
+
+ public async ValueTask DisposeAsync()
+ {
+ _disposed = true;
+
+ StopAutomaticProgress();
+
+ var controllers = SessionControllers.ToList();
+
+ foreach (var controller in controllers)
+ {
+ if (controller is IAsyncDisposable disposableAsync)
+ {
+ _logger.LogDebug("Disposing session controller asynchronously {TypeName}", disposableAsync.GetType().Name);
+ await disposableAsync.DisposeAsync().ConfigureAwait(false);
+ }
+ }
+ }
}
}
diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs
index a0c38b309..216494556 100644
--- a/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs
+++ b/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs
@@ -549,7 +549,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates
if (InitialState.Equals(GroupStateType.Playing))
{
- // Group went from playing to waiting state and a pause request occured while waiting.
+ // Group went from playing to waiting state and a pause request occurred while waiting.
var pauseRequest = new PauseGroupRequest();
pausedState.HandleRequest(pauseRequest, context, Type, session, cancellationToken);
}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs
index 2f38d6adc..619294e95 100644
--- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs
+++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs
@@ -27,9 +27,9 @@ namespace MediaBrowser.Controller.SyncPlay.PlaybackRequests
}
/// <summary>
- /// Gets the playlist identifiers ot the items.
+ /// Gets the playlist identifiers of the items.
/// </summary>
- /// <value>The playlist identifiers ot the items.</value>
+ /// <value>The playlist identifiers of the items.</value>
public IReadOnlyList<Guid> PlaylistItemIds { get; }
/// <summary>
diff --git a/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs
index f49876cca..3a7685f34 100644
--- a/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs
+++ b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs
@@ -102,7 +102,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
}
/// <summary>
- /// Appends new items to the playlist. The specified order is mantained.
+ /// Appends new items to the playlist. The specified order is maintained.
/// </summary>
/// <param name="items">The items to add to the playlist.</param>
public void Queue(IReadOnlyList<Guid> items)
@@ -197,7 +197,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
}
/// <summary>
- /// Adds new items to the playlist right after the playing item. The specified order is mantained.
+ /// Adds new items to the playlist right after the playing item. The specified order is maintained.
/// </summary>
/// <param name="items">The items to add to the playlist.</param>
public void QueueNext(IReadOnlyList<Guid> items)
diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
index 4a0624540..33b082731 100644
--- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
+++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
@@ -27,7 +27,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs
index 777fe6774..a9e1b4a51 100644
--- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs
+++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs
@@ -53,10 +53,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
/// <exception cref="ArgumentNullException">Item is null.</exception>
public void Fetch(MetadataResult<T> item, string metadataFile, CancellationToken cancellationToken)
{
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
+ ArgumentNullException.ThrowIfNull(item);
if (string.IsNullOrEmpty(metadataFile))
{
diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
index 142571e8f..22abf93ac 100644
--- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
+++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
@@ -52,10 +52,7 @@ namespace MediaBrowser.MediaEncoding.Attachments
/// <inheritdoc />
public async Task<(MediaAttachment Attachment, Stream Stream)> GetAttachment(BaseItem item, string mediaSourceId, int attachmentStreamIndex, CancellationToken cancellationToken)
{
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
+ ArgumentNullException.ThrowIfNull(item);
if (string.IsNullOrWhiteSpace(mediaSourceId))
{
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index 20d372d7a..9b4b1db94 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -100,6 +100,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
"scale_vaapi",
"deinterlace_vaapi",
"tonemap_vaapi",
+ "procamp_vaapi",
"overlay_vaapi",
"hwupload_vaapi"
};
@@ -152,7 +153,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
string output;
try
{
- output = GetProcessOutput(_encoderPath, "-version", false);
+ output = GetProcessOutput(_encoderPath, "-version", false, null);
}
catch (Exception ex)
{
@@ -233,7 +234,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
string output;
try
{
- output = GetProcessOutput(_encoderPath, "-version", false);
+ output = GetProcessOutput(_encoderPath, "-version", false, null);
}
catch (Exception ex)
{
@@ -340,7 +341,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
try
{
- var output = GetProcessOutput(_encoderPath, "-v verbose -hide_banner -init_hw_device vaapi=va:" + renderNodePath, true);
+ var output = GetProcessOutput(_encoderPath, "-v verbose -hide_banner -init_hw_device vaapi=va:" + renderNodePath, true, null);
return output.Contains(driverName, StringComparison.Ordinal);
}
catch (Exception ex)
@@ -355,7 +356,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
string? output = null;
try
{
- output = GetProcessOutput(_encoderPath, "-hwaccels", false);
+ output = GetProcessOutput(_encoderPath, "-hwaccels", false, null);
}
catch (Exception ex)
{
@@ -383,7 +384,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
string output;
try
{
- output = GetProcessOutput(_encoderPath, "-h filter=" + filter, false);
+ output = GetProcessOutput(_encoderPath, "-h filter=" + filter, false, null);
}
catch (Exception ex)
{
@@ -401,13 +402,34 @@ namespace MediaBrowser.MediaEncoding.Encoder
return false;
}
+ public bool CheckSupportedRuntimeKey(string keyDesc)
+ {
+ if (string.IsNullOrEmpty(keyDesc))
+ {
+ return false;
+ }
+
+ string output;
+ try
+ {
+ output = GetProcessOutput(_encoderPath, "-hide_banner -f lavfi -i nullsrc=s=1x1:d=500 -f null -", true, "?");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error checking supported runtime key");
+ return false;
+ }
+
+ return output.Contains(keyDesc, StringComparison.Ordinal);
+ }
+
private IEnumerable<string> GetCodecs(Codec codec)
{
string codecstr = codec == Codec.Encoder ? "encoders" : "decoders";
string output;
try
{
- output = GetProcessOutput(_encoderPath, "-" + codecstr, false);
+ output = GetProcessOutput(_encoderPath, "-" + codecstr, false, null);
}
catch (Exception ex)
{
@@ -438,7 +460,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
string output;
try
{
- output = GetProcessOutput(_encoderPath, "-filters", false);
+ output = GetProcessOutput(_encoderPath, "-filters", false, null);
}
catch (Exception ex)
{
@@ -476,7 +498,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
return dict;
}
- private string GetProcessOutput(string path, string arguments, bool readStdErr)
+ private string GetProcessOutput(string path, string arguments, bool readStdErr, string? testKey)
{
using (var process = new Process()
{
@@ -486,6 +508,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false,
+ RedirectStandardInput = !string.IsNullOrEmpty(testKey),
RedirectStandardOutput = true,
RedirectStandardError = true
}
@@ -495,6 +518,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
process.Start();
+ if (!string.IsNullOrEmpty(testKey))
+ {
+ process.StandardInput.Write(testKey);
+ }
+
return readStdErr ? process.StandardError.ReadToEnd() : process.StandardOutput.ReadToEnd();
}
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index c796ee780..757a01715 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -16,6 +16,7 @@ using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.MediaEncoding.Probing;
using MediaBrowser.Model.Dlna;
@@ -49,6 +50,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
private readonly IServerConfigurationManager _configurationManager;
private readonly IFileSystem _fileSystem;
private readonly ILocalizationManager _localization;
+ private readonly IConfiguration _config;
private readonly string _startupOptionFFmpegPath;
private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(2, 2);
@@ -65,6 +67,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
private List<string> _filters = new List<string>();
private IDictionary<int, bool> _filtersWithOption = new Dictionary<int, bool>();
+ private bool _isPkeyPauseSupported = false;
+
private bool _isVaapiDeviceAmd = false;
private bool _isVaapiDeviceInteliHD = false;
private bool _isVaapiDeviceInteli965 = false;
@@ -85,6 +89,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_configurationManager = configurationManager;
_fileSystem = fileSystem;
_localization = localization;
+ _config = config;
_startupOptionFFmpegPath = config.GetValue<string>(Controller.Extensions.ConfigurationExtensions.FfmpegPathKey) ?? string.Empty;
_jsonSerializerOptions = JsonDefaults.Options;
}
@@ -97,6 +102,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
public Version EncoderVersion => _ffmpegVersion;
+ public bool IsPkeyPauseSupported => _isPkeyPauseSupported;
+
public bool IsVaapiDeviceAmd => _isVaapiDeviceAmd;
public bool IsVaapiDeviceInteliHD => _isVaapiDeviceInteliHD;
@@ -151,6 +158,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
_threads = EncodingHelper.GetNumberOfThreads(null, options, null);
+ _isPkeyPauseSupported = validator.CheckSupportedRuntimeKey("p pause transcoding");
+
// Check the Vaapi device vendor
if (OperatingSystem.IsLinux()
&& SupportsHwaccel("vaapi")
@@ -371,12 +380,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
var inputFile = request.MediaSource.Path;
string analyzeDuration = string.Empty;
+ string ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
if (request.MediaSource.AnalyzeDurationMs > 0)
{
analyzeDuration = "-analyzeduration " +
(request.MediaSource.AnalyzeDurationMs * 1000).ToString();
}
+ else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
+ {
+ analyzeDuration = "-analyzeduration " + ffmpegAnalyzeDuration;
+ }
var forceEnableLogging = request.MediaSource.Protocol != MediaProtocol.File;
@@ -412,6 +426,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
/// <summary>
+ /// Gets the input argument for an external subtitle file.
+ /// </summary>
+ /// <param name="inputFile">The input file.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="ArgumentException">Unrecognized InputType.</exception>
+ public string GetExternalSubtitleInputArgument(string inputFile)
+ {
+ const string Prefix = "file";
+
+ return EncodingUtils.GetInputArgument(Prefix, inputFile, MediaProtocol.File);
+ }
+
+ /// <summary>
/// Gets the media info internal.
/// </summary>
/// <returns>Task{MediaInfoResult}.</returns>
@@ -598,9 +625,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
Video3DFormat.HalfSideBySide => "crop=iw/2:ih: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",
// fsbs crop width in half,set the display aspect,crop out any black bars we may have made
Video3DFormat.FullSideBySide => "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",
- // htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made
+ // htab crop height in half,scale to correct size, set the display aspect,crop out any black bars we may have made
Video3DFormat.HalfTopAndBottom => "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",
- // ftab crop heigt in half, set the display aspect,crop out any black bars we may have made
+ // ftab crop height in half, set the display aspect,crop out any black bars we may have made
Video3DFormat.FullTopAndBottom => "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=trunc(iw*sar):ih"
};
@@ -616,10 +643,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
filters.Add("thumbnail=n=" + (useLargerBatchSize ? "50" : "24"));
}
- // Use SW tonemap on HDR video stream only when the zscale filter is available.
- var enableHdrExtraction = string.Equals(videoStream?.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase) && SupportsFilter("zscale");
- if (enableHdrExtraction)
+ // Use SW tonemap on HDR10/HLG video stream only when the zscale filter is available.
+ var enableHdrExtraction = false;
+
+ if ((string.Equals(videoStream?.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream?.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
+ && SupportsFilter("zscale"))
{
+ enableHdrExtraction = true;
+
filters.Add("zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0:peak=100,zscale=t=bt709:m=bt709,format=yuv420p");
}
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index 7c70d37a6..afe4ff4e7 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -30,7 +30,7 @@
<PackageReference Include="libse" Version="3.6.5" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
- <PackageReference Include="UTF.Unknown" Version="2.5.0" />
+ <PackageReference Include="UTF.Unknown" Version="2.5.1" />
</ItemGroup>
<!-- Code Analyzers-->
@@ -40,7 +40,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
index a9e753726..205e84153 100644
--- a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
+++ b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
@@ -15,10 +15,7 @@ namespace MediaBrowser.MediaEncoding.Probing
/// <param name="result">The result.</param>
public static void NormalizeFFProbeResult(InternalMediaInfoResult result)
{
- if (result == null)
- {
- throw new ArgumentNullException(nameof(result));
- }
+ ArgumentNullException.ThrowIfNull(result);
if (result.Format?.Tags != null)
{
diff --git a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
index c9c8c34c2..eab8f79bb 100644
--- a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
+++ b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
@@ -310,5 +310,12 @@ namespace MediaBrowser.MediaEncoding.Probing
/// <value>The color primaries.</value>
[JsonPropertyName("color_primaries")]
public string ColorPrimaries { get; set; }
+
+ /// <summary>
+ /// Gets or sets the side_data_list.
+ /// </summary>
+ /// <value>The side_data_list.</value>
+ [JsonPropertyName("side_data_list")]
+ public IReadOnlyList<MediaStreamInfoSideData> SideDataList { get; set; }
}
}
diff --git a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfoSideData.cs b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfoSideData.cs
new file mode 100644
index 000000000..5dbc438e4
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfoSideData.cs
@@ -0,0 +1,73 @@
+using System.Text.Json.Serialization;
+
+namespace MediaBrowser.MediaEncoding.Probing
+{
+ /// <summary>
+ /// Class MediaStreamInfoSideData.
+ /// </summary>
+ public class MediaStreamInfoSideData
+ {
+ /// <summary>
+ /// Gets or sets the SideDataType.
+ /// </summary>
+ /// <value>The SideDataType.</value>
+ [JsonPropertyName("side_data_type")]
+ public string? SideDataType { get; set; }
+
+ /// <summary>
+ /// Gets or sets the DvVersionMajor.
+ /// </summary>
+ /// <value>The DvVersionMajor.</value>
+ [JsonPropertyName("dv_version_major")]
+ public int? DvVersionMajor { get; set; }
+
+ /// <summary>
+ /// Gets or sets the DvVersionMinor.
+ /// </summary>
+ /// <value>The DvVersionMinor.</value>
+ [JsonPropertyName("dv_version_minor")]
+ public int? DvVersionMinor { get; set; }
+
+ /// <summary>
+ /// Gets or sets the DvProfile.
+ /// </summary>
+ /// <value>The DvProfile.</value>
+ [JsonPropertyName("dv_profile")]
+ public int? DvProfile { get; set; }
+
+ /// <summary>
+ /// Gets or sets the DvLevel.
+ /// </summary>
+ /// <value>The DvLevel.</value>
+ [JsonPropertyName("dv_level")]
+ public int? DvLevel { get; set; }
+
+ /// <summary>
+ /// Gets or sets the RpuPresentFlag.
+ /// </summary>
+ /// <value>The RpuPresentFlag.</value>
+ [JsonPropertyName("rpu_present_flag")]
+ public int? RpuPresentFlag { get; set; }
+
+ /// <summary>
+ /// Gets or sets the ElPresentFlag.
+ /// </summary>
+ /// <value>The ElPresentFlag.</value>
+ [JsonPropertyName("el_present_flag")]
+ public int? ElPresentFlag { get; set; }
+
+ /// <summary>
+ /// Gets or sets the BlPresentFlag.
+ /// </summary>
+ /// <value>The BlPresentFlag.</value>
+ [JsonPropertyName("bl_present_flag")]
+ public int? BlPresentFlag { get; set; }
+
+ /// <summary>
+ /// Gets or sets the DvBlSignalCompatibilityId.
+ /// </summary>
+ /// <value>The DvBlSignalCompatibilityId.</value>
+ [JsonPropertyName("dv_bl_signal_compatibility_id")]
+ public int? DvBlSignalCompatibilityId { get; set; }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 3f78d0d42..b33b45ab2 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -44,16 +44,28 @@ namespace MediaBrowser.MediaEncoding.Probing
private IReadOnlyList<string> SplitWhitelist => _splitWhiteList ??= new string[]
{
"AC/DC",
+ "A/T/O/S",
"As/Hi Soundworks",
"Au/Ra",
"Bremer/McCoy",
+ "b/bqスタヂオ",
+ "DOV/S",
+ "DJ'TEKINA//SOMETHING",
+ "IX/ON",
+ "J-CORE SLi//CER",
+ "M(a/u)SH",
+ "Kaoru/Brilliance",
+ "signum/ii",
+ "Richiter(LORB/DUGEM DI BARAT)",
"이달의 소녀 1/3",
"R!N / Gemie",
"LOONA 1/3",
"LOONA / yyxy",
"LOONA / ODD EYE CIRCLE",
"K/DA",
- "22/7"
+ "22/7",
+ "諭吉佳作/men",
+ "//dARTH nULL"
};
public MediaInfo GetMediaInfo(InternalMediaInfoResult data, VideoType? videoType, bool isAudio, string path, MediaProtocol protocol)
@@ -841,9 +853,35 @@ namespace MediaBrowser.MediaEncoding.Probing
{
stream.ColorPrimaries = streamInfo.ColorPrimaries;
}
+
+ if (streamInfo.SideDataList != null)
+ {
+ foreach (var data in streamInfo.SideDataList)
+ {
+ // Parse Dolby Vision metadata from side_data
+ if (string.Equals(data.SideDataType, "DOVI configuration record", StringComparison.OrdinalIgnoreCase))
+ {
+ stream.DvVersionMajor = data.DvVersionMajor;
+ stream.DvVersionMinor = data.DvVersionMinor;
+ stream.DvProfile = data.DvProfile;
+ stream.DvLevel = data.DvLevel;
+ stream.RpuPresentFlag = data.RpuPresentFlag;
+ stream.ElPresentFlag = data.ElPresentFlag;
+ stream.BlPresentFlag = data.BlPresentFlag;
+ stream.DvBlSignalCompatibilityId = data.DvBlSignalCompatibilityId;
+
+ break;
+ }
+ }
+ }
+ }
+ else if (string.Equals(streamInfo.CodecType, "data", StringComparison.OrdinalIgnoreCase))
+ {
+ stream.Type = MediaStreamType.Data;
}
else
{
+ _logger.LogError("Codec Type {CodecType} unknown. The stream (index: {Index}) will be ignored. Warning: Subsequential streams will have a wrong stream specifier!", streamInfo.CodecType, streamInfo.Index);
return null;
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
deleted file mode 100644
index 08ee5c72e..000000000
--- a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using Microsoft.Extensions.Logging;
-using Nikse.SubtitleEdit.Core.SubtitleFormats;
-
-namespace MediaBrowser.MediaEncoding.Subtitles
-{
- /// <summary>
- /// Advanced SubStation Alpha subtitle parser.
- /// </summary>
- public class AssParser : SubtitleEditParser<AdvancedSubStationAlpha>
- {
- /// <summary>
- /// Initializes a new instance of the <see cref="AssParser"/> class.
- /// </summary>
- /// <param name="logger">The logger.</param>
- public AssParser(ILogger logger) : base(logger)
- {
- }
- }
-}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/AssWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/AssWriter.cs
new file mode 100644
index 000000000..0d1cf6e25
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Subtitles/AssWriter.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading;
+using MediaBrowser.Model.MediaInfo;
+
+namespace MediaBrowser.MediaEncoding.Subtitles
+{
+ /// <summary>
+ /// ASS subtitle writer.
+ /// </summary>
+ public class AssWriter : ISubtitleWriter
+ {
+ /// <inheritdoc />
+ public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
+ {
+ using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
+ {
+ var trackEvents = info.TrackEvents;
+ var timeFormat = @"hh\:mm\:ss\.ff";
+
+ // Write ASS header
+ writer.WriteLine("[Script Info]");
+ writer.WriteLine("Title: Jellyfin transcoded ASS subtitle");
+ writer.WriteLine("ScriptType: v4.00+");
+ writer.WriteLine();
+ writer.WriteLine("[V4+ Styles]");
+ writer.WriteLine("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding");
+ writer.WriteLine("Style: Default,Arial,20,&H00FFFFFF,&H00FFFFFF,&H19333333,&H910E0807,0,0,0,0,100,100,0,0,0,1,0,2,10,10,10,1");
+ writer.WriteLine();
+ writer.WriteLine("[Events]");
+ writer.WriteLine("Format: Layer, Start, End, Style, Text");
+
+ for (int i = 0; i < trackEvents.Count; i++)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var trackEvent = trackEvents[i];
+ var startTime = TimeSpan.FromTicks(trackEvent.StartPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture);
+ var endTime = TimeSpan.FromTicks(trackEvent.EndPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture);
+ var text = Regex.Replace(trackEvent.Text, @"\n", "\\n", RegexOptions.IgnoreCase);
+
+ writer.WriteLine(
+ "Dialogue: 0,{0},{1},Default,{2}",
+ startTime,
+ endTime,
+ text);
+ }
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs b/MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs
index c0023ebf2..bd13437fb 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs
@@ -1,7 +1,6 @@
#pragma warning disable CS1591
using System.IO;
-using System.Threading;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.MediaEncoding.Subtitles
@@ -12,8 +11,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// Parses the specified stream.
/// </summary>
/// <param name="stream">The stream.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
+ /// <param name="fileExtension">The file extension.</param>
/// <returns>SubtitleTrackInfo.</returns>
- SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken);
+ SubtitleTrackInfo Parse(Stream stream, string fileExtension);
+
+ /// <summary>
+ /// Determines whether the file extension is supported by the parser.
+ /// </summary>
+ /// <param name="fileExtension">The file extension.</param>
+ /// <returns>A value indicating whether the file extension is supported.</returns>
+ bool SupportsFileExtension(string fileExtension);
}
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs
deleted file mode 100644
index 78d54ca51..000000000
--- a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using Microsoft.Extensions.Logging;
-using Nikse.SubtitleEdit.Core.SubtitleFormats;
-
-namespace MediaBrowser.MediaEncoding.Subtitles
-{
- /// <summary>
- /// SubRip subtitle parser.
- /// </summary>
- public class SrtParser : SubtitleEditParser<SubRip>
- {
- /// <summary>
- /// Initializes a new instance of the <see cref="SrtParser"/> class.
- /// </summary>
- /// <param name="logger">The logger.</param>
- public SrtParser(ILogger logger) : base(logger)
- {
- }
- }
-}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
deleted file mode 100644
index 17c2ae40e..000000000
--- a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using Microsoft.Extensions.Logging;
-using Nikse.SubtitleEdit.Core.SubtitleFormats;
-
-namespace MediaBrowser.MediaEncoding.Subtitles
-{
- /// <summary>
- /// SubStation Alpha subtitle parser.
- /// </summary>
- public class SsaParser : SubtitleEditParser<SubStationAlpha>
- {
- /// <summary>
- /// Initializes a new instance of the <see cref="SsaParser"/> class.
- /// </summary>
- /// <param name="logger">The logger.</param>
- public SsaParser(ILogger logger) : base(logger)
- {
- }
- }
-}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SsaWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/SsaWriter.cs
new file mode 100644
index 000000000..6761cd309
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Subtitles/SsaWriter.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading;
+using MediaBrowser.Model.MediaInfo;
+
+namespace MediaBrowser.MediaEncoding.Subtitles
+{
+ /// <summary>
+ /// SSA subtitle writer.
+ /// </summary>
+ public class SsaWriter : ISubtitleWriter
+ {
+ /// <inheritdoc />
+ public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
+ {
+ using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
+ {
+ var trackEvents = info.TrackEvents;
+ var timeFormat = @"hh\:mm\:ss\.ff";
+
+ // Write SSA header
+ writer.WriteLine("[Script Info]");
+ writer.WriteLine("Title: Jellyfin transcoded SSA subtitle");
+ writer.WriteLine("ScriptType: v4.00");
+ writer.WriteLine();
+ writer.WriteLine("[V4 Styles]");
+ writer.WriteLine("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
+ writer.WriteLine("Style: Default,Arial,20,&H00FFFFFF,&H00FFFFFF,&H19333333,&H19333333,0,0,0,1,0,2,10,10,10,0,1");
+ writer.WriteLine();
+ writer.WriteLine("[Events]");
+ writer.WriteLine("Format: Layer, Start, End, Style, Text");
+
+ for (int i = 0; i < trackEvents.Count; i++)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var trackEvent = trackEvents[i];
+ var startTime = TimeSpan.FromTicks(trackEvent.StartPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture);
+ var endTime = TimeSpan.FromTicks(trackEvent.EndPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture);
+ var text = Regex.Replace(trackEvent.Text, @"\n", "\\n", RegexOptions.IgnoreCase);
+
+ writer.WriteLine(
+ "Dialogue: 0,{0},{1},Default,{2}",
+ startTime,
+ endTime,
+ text);
+ }
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs
index 52c1b6467..0d4489517 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs
@@ -1,12 +1,12 @@
+using System;
+using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
-using System.Threading;
using Jellyfin.Extensions;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
using Nikse.SubtitleEdit.Core.Common;
-using ILogger = Microsoft.Extensions.Logging.ILogger;
using SubtitleFormat = Nikse.SubtitleEdit.Core.SubtitleFormats.SubtitleFormat;
namespace MediaBrowser.MediaEncoding.Subtitles
@@ -14,31 +14,57 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <summary>
/// SubStation Alpha subtitle parser.
/// </summary>
- /// <typeparam name="T">The <see cref="SubtitleFormat" />.</typeparam>
- public abstract class SubtitleEditParser<T> : ISubtitleParser
- where T : SubtitleFormat, new()
+ public class SubtitleEditParser : ISubtitleParser
{
- private readonly ILogger _logger;
+ private readonly ILogger<SubtitleEditParser> _logger;
+ private readonly Dictionary<string, SubtitleFormat[]> _subtitleFormats;
/// <summary>
- /// Initializes a new instance of the <see cref="SubtitleEditParser{T}"/> class.
+ /// Initializes a new instance of the <see cref="SubtitleEditParser"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
- protected SubtitleEditParser(ILogger logger)
+ public SubtitleEditParser(ILogger<SubtitleEditParser> logger)
{
_logger = logger;
+ _subtitleFormats = GetSubtitleFormats()
+ .Where(subtitleFormat => !string.IsNullOrEmpty(subtitleFormat.Extension))
+ .GroupBy(subtitleFormat => subtitleFormat.Extension.TrimStart('.'), StringComparer.OrdinalIgnoreCase)
+ .ToDictionary(g => g.Key, g => g.ToArray(), StringComparer.OrdinalIgnoreCase);
}
/// <inheritdoc />
- public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
+ public SubtitleTrackInfo Parse(Stream stream, string fileExtension)
{
var subtitle = new Subtitle();
- var subRip = new T();
var lines = stream.ReadAllLines().ToList();
- subRip.LoadSubtitle(subtitle, lines, "untitled");
- if (subRip.ErrorCount > 0)
+
+ if (!_subtitleFormats.TryGetValue(fileExtension, out var subtitleFormats))
+ {
+ throw new ArgumentException($"Unsupported file extension: {fileExtension}", nameof(fileExtension));
+ }
+
+ foreach (var subtitleFormat in subtitleFormats)
{
- _logger.LogError("{ErrorCount} errors encountered while parsing subtitle", subRip.ErrorCount);
+ _logger.LogDebug(
+ "Trying to parse '{FileExtension}' subtitle using the {SubtitleFormatParser} format parser",
+ fileExtension,
+ subtitleFormat.Name);
+ subtitleFormat.LoadSubtitle(subtitle, lines, fileExtension);
+ if (subtitleFormat.ErrorCount == 0)
+ {
+ break;
+ }
+
+ _logger.LogError(
+ "{ErrorCount} errors encountered while parsing '{FileExtension}' subtitle using the {SubtitleFormatParser} format parser",
+ subtitleFormat.ErrorCount,
+ fileExtension,
+ subtitleFormat.Name);
+ }
+
+ if (subtitle.Paragraphs.Count == 0)
+ {
+ throw new ArgumentException("Unsupported format: " + fileExtension);
}
var trackInfo = new SubtitleTrackInfo();
@@ -57,5 +83,36 @@ namespace MediaBrowser.MediaEncoding.Subtitles
trackInfo.TrackEvents = trackEvents;
return trackInfo;
}
+
+ /// <inheritdoc />
+ public bool SupportsFileExtension(string fileExtension)
+ => _subtitleFormats.ContainsKey(fileExtension);
+
+ private IEnumerable<SubtitleFormat> GetSubtitleFormats()
+ {
+ var subtitleFormats = new List<SubtitleFormat>();
+ var assembly = typeof(SubtitleFormat).Assembly;
+
+ foreach (var type in assembly.GetTypes())
+ {
+ if (!type.IsSubclassOf(typeof(SubtitleFormat)) || type.IsAbstract)
+ {
+ continue;
+ }
+
+ try
+ {
+ // It shouldn't be null, but the exception is caught if it is
+ var subtitleFormat = (SubtitleFormat)Activator.CreateInstance(type, true)!;
+ subtitleFormats.Add(subtitleFormat);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Failed to create instance of the subtitle format {SubtitleFormatType}", type.Name);
+ }
+ }
+
+ return subtitleFormats;
+ }
}
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index f4842d368..9185faf67 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -35,6 +35,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
private readonly IMediaEncoder _mediaEncoder;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IMediaSourceManager _mediaSourceManager;
+ private readonly ISubtitleParser _subtitleParser;
/// <summary>
/// The _semaphoreLocks.
@@ -48,7 +49,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
IFileSystem fileSystem,
IMediaEncoder mediaEncoder,
IHttpClientFactory httpClientFactory,
- IMediaSourceManager mediaSourceManager)
+ IMediaSourceManager mediaSourceManager,
+ ISubtitleParser subtitleParser)
{
_logger = logger;
_appPaths = appPaths;
@@ -56,6 +58,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
_mediaEncoder = mediaEncoder;
_httpClientFactory = httpClientFactory;
_mediaSourceManager = mediaSourceManager;
+ _subtitleParser = subtitleParser;
}
private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles");
@@ -73,8 +76,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
try
{
- var reader = GetReader(inputFormat);
- var trackInfo = reader.Parse(stream, cancellationToken);
+ var trackInfo = _subtitleParser.Parse(stream, inputFormat);
FilterEvents(trackInfo, startTimeTicks, endTimeTicks, preserveOriginalTimestamps);
@@ -118,10 +120,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
async Task<Stream> ISubtitleEncoder.GetSubtitles(BaseItem item, string mediaSourceId, int subtitleStreamIndex, string outputFormat, long startTimeTicks, long endTimeTicks, bool preserveOriginalTimestamps, CancellationToken cancellationToken)
{
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
+ ArgumentNullException.ThrowIfNull(item);
if (string.IsNullOrWhiteSpace(mediaSourceId))
{
@@ -195,7 +194,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
MediaStream subtitleStream,
CancellationToken cancellationToken)
{
- if (!subtitleStream.IsExternal)
+ if (!subtitleStream.IsExternal || subtitleStream.Path.EndsWith(".mks", StringComparison.OrdinalIgnoreCase))
{
string outputFormat;
string outputCodec;
@@ -224,7 +223,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
// Extract
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + outputFormat);
- await ExtractTextSubtitle(mediaSource, subtitleStream.Index, outputCodec, outputPath, cancellationToken)
+ await ExtractTextSubtitle(mediaSource, subtitleStream, outputCodec, outputPath, cancellationToken)
.ConfigureAwait(false);
return new SubtitleInfo(outputPath, MediaProtocol.File, outputFormat, false);
@@ -233,56 +232,29 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var currentFormat = (Path.GetExtension(subtitleStream.Path) ?? subtitleStream.Codec)
.TrimStart('.');
- if (!TryGetReader(currentFormat, out _))
+ // Fallback to ffmpeg conversion
+ if (!_subtitleParser.SupportsFileExtension(currentFormat))
{
// Convert
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, ".srt");
- await ConvertTextSubtitleToSrt(subtitleStream.Path, subtitleStream.Language, mediaSource, outputPath, cancellationToken).ConfigureAwait(false);
+ await ConvertTextSubtitleToSrt(subtitleStream, mediaSource, outputPath, cancellationToken).ConfigureAwait(false);
return new SubtitleInfo(outputPath, MediaProtocol.File, "srt", true);
}
- // It's possbile that the subtitleStream and mediaSource don't share the same protocol (e.g. .STRM file with local subs)
+ // It's possible that the subtitleStream and mediaSource don't share the same protocol (e.g. .STRM file with local subs)
return new SubtitleInfo(subtitleStream.Path, _mediaSourceManager.GetPathProtocol(subtitleStream.Path), currentFormat, true);
}
- private bool TryGetReader(string format, [NotNullWhen(true)] out ISubtitleParser? value)
+ private bool TryGetWriter(string format, [NotNullWhen(true)] out ISubtitleWriter? value)
{
- if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase))
- {
- value = new SrtParser(_logger);
- return true;
- }
-
- if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase))
- {
- value = new SsaParser(_logger);
- return true;
- }
-
if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase))
{
- value = new AssParser(_logger);
+ value = new AssWriter();
return true;
}
- value = null;
- return false;
- }
-
- private ISubtitleParser GetReader(string format)
- {
- if (TryGetReader(format, out var reader))
- {
- return reader;
- }
-
- throw new ArgumentException("Unsupported format: " + format);
- }
-
- private bool TryGetWriter(string format, [NotNullWhen(true)] out ISubtitleWriter? value)
- {
if (string.IsNullOrEmpty(format))
{
throw new ArgumentNullException(nameof(format));
@@ -294,12 +266,18 @@ namespace MediaBrowser.MediaEncoding.Subtitles
return true;
}
- if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase) || string.Equals(format, SubtitleFormat.SUBRIP, StringComparison.OrdinalIgnoreCase))
{
value = new SrtWriter();
return true;
}
+ if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase))
+ {
+ value = new SsaWriter();
+ return true;
+ }
+
if (string.Equals(format, SubtitleFormat.VTT, StringComparison.OrdinalIgnoreCase))
{
value = new VttWriter();
@@ -339,13 +317,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <summary>
/// Converts the text subtitle to SRT.
/// </summary>
- /// <param name="inputPath">The input path.</param>
- /// <param name="language">The language.</param>
+ /// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="mediaSource">The input mediaSource.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- private async Task ConvertTextSubtitleToSrt(string inputPath, string language, MediaSourceInfo mediaSource, string outputPath, CancellationToken cancellationToken)
+ private async Task ConvertTextSubtitleToSrt(MediaStream subtitleStream, MediaSourceInfo mediaSource, string outputPath, CancellationToken cancellationToken)
{
var semaphore = GetLock(outputPath);
@@ -355,7 +332,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
if (!File.Exists(outputPath))
{
- await ConvertTextSubtitleToSrtInternal(inputPath, language, mediaSource, outputPath, cancellationToken).ConfigureAwait(false);
+ await ConvertTextSubtitleToSrtInternal(subtitleStream, mediaSource, outputPath, cancellationToken).ConfigureAwait(false);
}
}
finally
@@ -367,8 +344,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <summary>
/// Converts the text subtitle to SRT internal.
/// </summary>
- /// <param name="inputPath">The input path.</param>
- /// <param name="language">The language.</param>
+ /// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="mediaSource">The input mediaSource.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
@@ -376,8 +352,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <exception cref="ArgumentNullException">
/// The <c>inputPath</c> or <c>outputPath</c> is <c>null</c>.
/// </exception>
- private async Task ConvertTextSubtitleToSrtInternal(string inputPath, string language, MediaSourceInfo mediaSource, string outputPath, CancellationToken cancellationToken)
+ private async Task ConvertTextSubtitleToSrtInternal(MediaStream subtitleStream, MediaSourceInfo mediaSource, string outputPath, CancellationToken cancellationToken)
{
+ var inputPath = subtitleStream.Path;
if (string.IsNullOrEmpty(inputPath))
{
throw new ArgumentNullException(nameof(inputPath));
@@ -390,7 +367,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
Directory.CreateDirectory(Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath)));
- var encodingParam = await GetSubtitleFileCharacterSet(inputPath, language, mediaSource.Protocol, cancellationToken).ConfigureAwait(false);
+ var encodingParam = await GetSubtitleFileCharacterSet(subtitleStream, subtitleStream.Language, mediaSource, cancellationToken).ConfigureAwait(false);
// FFmpeg automatically convert character encoding when it is UTF-16
// If we specify character encoding, it rejects with "do not specify a character encoding" and "Unable to recode subtitle event"
@@ -408,18 +385,18 @@ namespace MediaBrowser.MediaEncoding.Subtitles
int exitCode;
using (var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
{
- StartInfo = new ProcessStartInfo
- {
- CreateNoWindow = true,
- UseShellExecute = false,
- FileName = _mediaEncoder.EncoderPath,
- Arguments = string.Format(CultureInfo.InvariantCulture, "{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
- WindowStyle = ProcessWindowStyle.Hidden,
- ErrorDialog = false
- },
- EnableRaisingEvents = true
- })
+ CreateNoWindow = true,
+ UseShellExecute = false,
+ FileName = _mediaEncoder.EncoderPath,
+ Arguments = string.Format(CultureInfo.InvariantCulture, "{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false
+ },
+ EnableRaisingEvents = true
+ })
{
_logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
@@ -494,7 +471,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// Extracts the text subtitle.
/// </summary>
/// <param name="mediaSource">The mediaSource.</param>
- /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
+ /// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="outputCodec">The output codec.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
@@ -502,7 +479,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <exception cref="ArgumentException">Must use inputPath list overload.</exception>
private async Task ExtractTextSubtitle(
MediaSourceInfo mediaSource,
- int subtitleStreamIndex,
+ MediaStream subtitleStream,
string outputCodec,
string outputPath,
CancellationToken cancellationToken)
@@ -511,12 +488,21 @@ namespace MediaBrowser.MediaEncoding.Subtitles
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
+ var subtitleStreamIndex = EncodingHelper.FindIndex(mediaSource.MediaStreams, subtitleStream);
+
try
{
if (!File.Exists(outputPath))
{
+ var args = _mediaEncoder.GetInputArgument(mediaSource.Path, mediaSource);
+
+ if (subtitleStream.IsExternal)
+ {
+ args = _mediaEncoder.GetExternalSubtitleInputArgument(subtitleStream.Path);
+ }
+
await ExtractTextSubtitleInternal(
- _mediaEncoder.GetInputArgument(mediaSource.Path, mediaSource),
+ args,
subtitleStreamIndex,
outputCodec,
outputPath,
@@ -550,7 +536,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var processArgs = string.Format(
CultureInfo.InvariantCulture,
- "-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"",
+ "-i {0} -copyts -map 0:{1} -an -vn -c:s {2} \"{3}\"",
inputPath,
subtitleStreamIndex,
outputCodec,
@@ -559,18 +545,18 @@ namespace MediaBrowser.MediaEncoding.Subtitles
int exitCode;
using (var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
{
- StartInfo = new ProcessStartInfo
- {
- CreateNoWindow = true,
- UseShellExecute = false,
- FileName = _mediaEncoder.EncoderPath,
- Arguments = processArgs,
- WindowStyle = ProcessWindowStyle.Hidden,
- ErrorDialog = false
- },
- EnableRaisingEvents = true
- })
+ CreateNoWindow = true,
+ UseShellExecute = false,
+ FileName = _mediaEncoder.EncoderPath,
+ Arguments = processArgs,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false
+ },
+ EnableRaisingEvents = true
+ })
{
_logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
@@ -585,7 +571,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
throw;
}
- var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
+ var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false);
if (!ranToCompletion)
{
@@ -672,11 +658,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles
if (!string.Equals(text, newText, StringComparison.Ordinal))
{
var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
- var writer = new StreamWriter(fileStream, encoding);
await using (fileStream.ConfigureAwait(false))
- await using (writer.ConfigureAwait(false))
{
- await writer.WriteAsync(newText.AsMemory(), cancellationToken).ConfigureAwait(false);
+ var writer = new StreamWriter(fileStream, encoding);
+ await using (writer.ConfigureAwait(false))
+ {
+ await writer.WriteAsync(newText.AsMemory(), cancellationToken).ConfigureAwait(false);
+ }
}
}
}
@@ -706,9 +694,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
/// <inheritdoc />
- public async Task<string> GetSubtitleFileCharacterSet(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken)
+ public async Task<string> GetSubtitleFileCharacterSet(MediaStream subtitleStream, string language, MediaSourceInfo mediaSource, CancellationToken cancellationToken)
{
- using (var stream = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false))
+ var subtitleCodec = subtitleStream.Codec;
+ var path = subtitleStream.Path;
+
+ if (path.EndsWith(".mks", StringComparison.OrdinalIgnoreCase))
+ {
+ path = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + subtitleCodec);
+ await ExtractTextSubtitle(mediaSource, subtitleStream, subtitleCodec, path, cancellationToken)
+ .ConfigureAwait(false);
+ }
+
+ using (var stream = await GetStream(path, mediaSource.Protocol, cancellationToken).ConfigureAwait(false))
{
var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName ?? string.Empty;
@@ -731,12 +729,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
switch (protocol)
{
case MediaProtocol.Http:
- {
- using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
- .GetAsync(new Uri(path), cancellationToken)
- .ConfigureAwait(false);
- return await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- }
+ {
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
+ .GetAsync(new Uri(path), cancellationToken)
+ .ConfigureAwait(false);
+ return await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+ }
case MediaProtocol.File:
return AsyncFile.OpenRead(path);
@@ -745,7 +743,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
}
- internal readonly struct SubtitleInfo
+ public readonly struct SubtitleInfo
{
public SubtitleInfo(string path, MediaProtocol protocol, string format, bool isExternal)
{
diff --git a/MediaBrowser.Model/Branding/BrandingOptions.cs b/MediaBrowser.Model/Branding/BrandingOptions.cs
index cc42c1718..695267d46 100644
--- a/MediaBrowser.Model/Branding/BrandingOptions.cs
+++ b/MediaBrowser.Model/Branding/BrandingOptions.cs
@@ -1,5 +1,4 @@
using System.Text.Json.Serialization;
-using System.Xml.Serialization;
namespace MediaBrowser.Model.Branding;
@@ -21,6 +20,11 @@ public class BrandingOptions
public string? CustomCss { get; set; }
/// <summary>
+ /// Gets or sets a value indicating whether to enable the splashscreen.
+ /// </summary>
+ public bool SplashscreenEnabled { get; set; } = true;
+
+ /// <summary>
/// Gets or sets the splashscreen location on disk.
/// </summary>
/// <remarks>
diff --git a/MediaBrowser.Model/Configuration/EmbeddedSubtitleOptions.cs b/MediaBrowser.Model/Configuration/EmbeddedSubtitleOptions.cs
index 42f07dbff..98e0f84e3 100644
--- a/MediaBrowser.Model/Configuration/EmbeddedSubtitleOptions.cs
+++ b/MediaBrowser.Model/Configuration/EmbeddedSubtitleOptions.cs
@@ -5,7 +5,6 @@ namespace MediaBrowser.Model.Configuration
/// </summary>
public enum EmbeddedSubtitleOptions
{
-
/// <summary>
/// Allow all embedded subs.
/// </summary>
@@ -26,5 +25,4 @@ namespace MediaBrowser.Model.Configuration
/// </summary>
AllowNone = 3,
}
-
}
diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs
index 06931ac3b..f4cd2f006 100644
--- a/MediaBrowser.Model/Configuration/EncodingOptions.cs
+++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs
@@ -1,5 +1,3 @@
-using System;
-
#nullable disable
#pragma warning disable CS1591
@@ -26,6 +24,8 @@ namespace MediaBrowser.Model.Configuration
TonemappingThreshold = 0.8;
TonemappingPeak = 100;
TonemappingParam = 0;
+ VppTonemappingBrightness = 0;
+ VppTonemappingContrast = 1.2;
H264Crf = 23;
H265Crf = 28;
DeinterlaceDoubleRate = false;
@@ -39,7 +39,7 @@ namespace MediaBrowser.Model.Configuration
EnableHardwareEncoding = true;
AllowHevcEncoding = false;
EnableSubtitleExtraction = true;
- AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = Array.Empty<string>();
+ AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = new[] { "mkv" };
HardwareDecodingCodecs = new string[] { "h264", "vc1" };
}
@@ -89,6 +89,10 @@ namespace MediaBrowser.Model.Configuration
public double TonemappingParam { get; set; }
+ public double VppTonemappingBrightness { get; set; }
+
+ public double VppTonemappingContrast { get; set; }
+
public int H264Crf { get; set; }
public int H265Crf { get; set; }
diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs
index ad3bce86e..c4d313bdb 100644
--- a/MediaBrowser.Model/Configuration/LibraryOptions.cs
+++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs
@@ -17,7 +17,7 @@ namespace MediaBrowser.Model.Configuration
RequirePerfectSubtitleMatch = true;
AllowEmbeddedSubtitles = EmbeddedSubtitleOptions.AllowAll;
- AutomaticallyAddToCollection = true;
+ AutomaticallyAddToCollection = false;
EnablePhotos = true;
SaveSubtitlesWithMedia = true;
EnableRealtimeMonitor = true;
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 46e61ee1a..e61b896b9 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -79,7 +79,7 @@ namespace MediaBrowser.Model.Configuration
/// <summary>
/// Gets or sets a value indicating whether quick connect is available for use on this server.
/// </summary>
- public bool QuickConnectAvailable { get; set; } = false;
+ public bool QuickConnectAvailable { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether [enable case sensitive item ids].
diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs
index 81359462c..94f354660 100644
--- a/MediaBrowser.Model/Configuration/UserConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs
@@ -22,10 +22,10 @@ namespace MediaBrowser.Model.Configuration
HidePlayedInLatest = true;
PlayDefaultAudioTrack = true;
- LatestItemsExcludes = Array.Empty<string>();
- OrderedViews = Array.Empty<string>();
- MyMediaExcludes = Array.Empty<string>();
- GroupedFolders = Array.Empty<string>();
+ LatestItemsExcludes = Array.Empty<Guid>();
+ OrderedViews = Array.Empty<Guid>();
+ MyMediaExcludes = Array.Empty<Guid>();
+ GroupedFolders = Array.Empty<Guid>();
}
/// <summary>
@@ -48,7 +48,7 @@ namespace MediaBrowser.Model.Configuration
public bool DisplayMissingEpisodes { get; set; }
- public string[] GroupedFolders { get; set; }
+ public Guid[] GroupedFolders { get; set; }
public SubtitlePlaybackMode SubtitleMode { get; set; }
@@ -56,11 +56,11 @@ namespace MediaBrowser.Model.Configuration
public bool EnableLocalPassword { get; set; }
- public string[] OrderedViews { get; set; }
+ public Guid[] OrderedViews { get; set; }
- public string[] LatestItemsExcludes { get; set; }
+ public Guid[] LatestItemsExcludes { get; set; }
- public string[] MyMediaExcludes { get; set; }
+ public Guid[] MyMediaExcludes { get; set; }
public bool HidePlayedInLatest { get; set; }
diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs
index eec541041..32a34d23c 100644
--- a/MediaBrowser.Model/Cryptography/PasswordHash.cs
+++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs
@@ -29,10 +29,7 @@ namespace MediaBrowser.Model.Cryptography
public PasswordHash(string id, byte[] hash, byte[] salt, Dictionary<string, string> parameters)
{
- if (id == null)
- {
- throw new ArgumentNullException(nameof(id));
- }
+ ArgumentNullException.ThrowIfNull(id);
if (id.Length == 0)
{
diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs
index 8d03b4c0b..573422416 100644
--- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs
+++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs
@@ -16,6 +16,7 @@ namespace MediaBrowser.Model.Dlna
int? videoBitDepth,
int? videoBitrate,
string? videoProfile,
+ string? videoRangeType,
double? videoLevel,
float? videoFramerate,
int? packetLength,
@@ -42,6 +43,8 @@ namespace MediaBrowser.Model.Dlna
return IsConditionSatisfied(condition, videoLevel);
case ProfileConditionValue.VideoProfile:
return IsConditionSatisfied(condition, videoProfile);
+ case ProfileConditionValue.VideoRangeType:
+ return IsConditionSatisfied(condition, videoRangeType);
case ProfileConditionValue.VideoCodecTag:
return IsConditionSatisfied(condition, videoCodecTag);
case ProfileConditionValue.PacketLength:
diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
index 6e129246b..1a9576361 100644
--- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
+++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
@@ -8,7 +8,7 @@ using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.Model.Dlna
{
- public class ContentFeatureBuilder
+ public static class ContentFeatureBuilder
{
public static string BuildImageHeader(
DeviceProfile profile,
@@ -128,6 +128,7 @@ namespace MediaBrowser.Model.Dlna
bool isDirectStream,
long? runtimeTicks,
string videoProfile,
+ string videoRangeType,
double? videoLevel,
float? videoFramerate,
int? packetLength,
@@ -156,7 +157,7 @@ namespace MediaBrowser.Model.Dlna
flagValue |= DlnaFlags.ByteBasedSeek;
}
- // Time based seek is curently disabled when streaming. On LG CX3 adding DlnaFlags.TimeBasedSeek and orgPn causes the DLNA playback to fail (format not supported). Further investigations are needed before enabling the remaining code paths.
+ // Time based seek is currently disabled when streaming. On LG CX3 adding DlnaFlags.TimeBasedSeek and orgPn causes the DLNA playback to fail (format not supported). Further investigations are needed before enabling the remaining code paths.
// else if (runtimeTicks.HasValue)
// {
// flagValue = flagValue | DlnaFlags.TimeBasedSeek;
@@ -176,6 +177,7 @@ namespace MediaBrowser.Model.Dlna
bitDepth,
videoBitrate,
videoProfile,
+ videoRangeType,
videoLevel,
videoFramerate,
packetLength,
diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs
index 6170ff5bd..79ae95170 100644
--- a/MediaBrowser.Model/Dlna/DeviceProfile.cs
+++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs
@@ -423,6 +423,7 @@ namespace MediaBrowser.Model.Dlna
/// <param name="bitDepth">The bit depth.</param>
/// <param name="videoBitrate">The video bitrate.</param>
/// <param name="videoProfile">The video profile.</param>
+ /// <param name="videoRangeType">The video range type.</param>
/// <param name="videoLevel">The video level.</param>
/// <param name="videoFramerate">The video framerate.</param>
/// <param name="packetLength">The packet length.</param>
@@ -444,6 +445,7 @@ namespace MediaBrowser.Model.Dlna
int? bitDepth,
int? videoBitrate,
string videoProfile,
+ string videoRangeType,
double? videoLevel,
float? videoFramerate,
int? packetLength,
@@ -483,7 +485,7 @@ namespace MediaBrowser.Model.Dlna
var anyOff = false;
foreach (ProfileCondition c in i.Conditions)
{
- if (!ConditionProcessor.IsVideoConditionSatisfied(GetModelProfileCondition(c), width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
+ if (!ConditionProcessor.IsVideoConditionSatisfied(GetModelProfileCondition(c), width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
{
anyOff = true;
break;
diff --git a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs
index eb81fde75..a32433e18 100644
--- a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs
+++ b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs
@@ -26,6 +26,7 @@ namespace MediaBrowser.Model.Dlna
IsAvc = 20,
IsInterlaced = 21,
AudioSampleRate = 22,
- AudioBitDepth = 23
+ AudioBitDepth = 23,
+ VideoRangeType = 24
}
}
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index 8671e5cbb..b121a2905 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -221,6 +221,9 @@ namespace MediaBrowser.Model.Dlna
case ProfileConditionValue.VideoProfile:
return TranscodeReason.VideoProfileNotSupported;
+ case ProfileConditionValue.VideoRangeType:
+ return TranscodeReason.VideoRangeTypeNotSupported;
+
case ProfileConditionValue.VideoTimestamp:
// TODO
return 0;
@@ -385,7 +388,7 @@ namespace MediaBrowser.Model.Dlna
// If device requirements are satisfied then allow both direct stream and direct play
if (item.SupportsDirectPlay)
{
- if (IsItemBitrateEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectPlay))
+ if (IsItemBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectPlay))
{
if (options.EnableDirectPlay)
{
@@ -401,7 +404,7 @@ namespace MediaBrowser.Model.Dlna
// While options takes the network and other factors into account. Only applies to direct stream
if (item.SupportsDirectStream)
{
- if (IsItemBitrateEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream))
+ if (IsItemBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream))
{
if (options.EnableDirectStream)
{
@@ -562,10 +565,7 @@ namespace MediaBrowser.Model.Dlna
private StreamInfo BuildVideoItem(MediaSourceInfo item, VideoOptions options)
{
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
+ ArgumentNullException.ThrowIfNull(item);
StreamInfo playlistItem = new StreamInfo
{
@@ -604,11 +604,11 @@ namespace MediaBrowser.Model.Dlna
var videoStream = item.VideoStream;
- var directPlayEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectPlay);
- var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectStream);
- bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult == 0);
- bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directPlayEligibilityResult == 0);
- var transcodeReasons = directPlayEligibilityResult | directStreamEligibilityResult;
+ var directPlayBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectPlay);
+ var directStreamBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectStream);
+ bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayBitrateEligibility == 0);
+ bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamBitrateEligibility == 0);
+ var transcodeReasons = directPlayBitrateEligibility | directStreamBitrateEligibility;
_logger.LogDebug(
"Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
@@ -625,7 +625,7 @@ namespace MediaBrowser.Model.Dlna
var directPlay = directPlayInfo.PlayMethod;
transcodeReasons |= directPlayInfo.TranscodeReasons;
- if (directPlay != null)
+ if (directPlay.HasValue)
{
directPlayProfile = directPlayInfo.Profile;
playlistItem.PlayMethod = directPlay.Value;
@@ -676,7 +676,7 @@ namespace MediaBrowser.Model.Dlna
playlistItem.TranscodeReasons = transcodeReasons;
- if (playlistItem.PlayMethod != PlayMethod.DirectStream || !options.EnableDirectStream)
+ if (playlistItem.PlayMethod != PlayMethod.DirectStream && playlistItem.PlayMethod != PlayMethod.DirectPlay)
{
// Can't direct play, find the transcoding profile
// If we do this for direct-stream we will overwrite the info
@@ -687,6 +687,8 @@ namespace MediaBrowser.Model.Dlna
BuildStreamVideoItem(playlistItem, options, item, videoStream, audioStream, candidateAudioStreams, transcodingProfile.Container, transcodingProfile.VideoCodec, transcodingProfile.AudioCodec);
+ playlistItem.PlayMethod = PlayMethod.Transcode;
+
if (subtitleStream != null)
{
var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode, _transcoderSupport, transcodingProfile.Container, transcodingProfile.Protocol);
@@ -696,14 +698,9 @@ namespace MediaBrowser.Model.Dlna
playlistItem.SubtitleCodecs = new[] { subtitleProfile.Format };
}
- if (playlistItem.PlayMethod != PlayMethod.DirectPlay)
+ if ((playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0)
{
- playlistItem.PlayMethod = PlayMethod.Transcode;
-
- if ((playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0)
- {
- ApplyTranscodingConditions(playlistItem, transcodingProfile.Conditions, null, true, true);
- }
+ ApplyTranscodingConditions(playlistItem, transcodingProfile.Conditions, null, true, true);
}
}
}
@@ -744,16 +741,19 @@ namespace MediaBrowser.Model.Dlna
{
var videoCodecs = ContainerProfile.SplitValue(transcodingProfile.VideoCodec);
- if (ContainerProfile.ContainsContainer(videoCodecs, item.VideoStream.Codec))
+ if (ContainerProfile.ContainsContainer(videoCodecs, item.VideoStream?.Codec))
{
var videoCodec = transcodingProfile.VideoCodec;
var container = transcodingProfile.Container;
var appliedVideoConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.Video &&
- i.ContainsAnyCodec(videoCodec, container))
+ i.ContainsAnyCodec(videoCodec, container) &&
+ i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.VideoRangeType, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)))
.Select(i =>
- i.ApplyConditions.Any(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)));
- var conditionsSatisfied = !appliedVideoConditions.Any() || !appliedVideoConditions.Any(satisfied => !satisfied);
+ i.Conditions.All(condition => ConditionProcessor.IsVideoConditionSatisfied(condition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.VideoRangeType, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)));
+
+ // An empty appliedVideoConditions means that the codec has no conditions for the current video stream
+ var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied);
return conditionsSatisfied ? 1 : 2;
}
@@ -768,35 +768,42 @@ namespace MediaBrowser.Model.Dlna
private void BuildStreamVideoItem(StreamInfo playlistItem, VideoOptions options, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable<MediaStream> candidateAudioStreams, string container, string videoCodec, string audioCodec)
{
- // prefer matching video codecs
+ // Prefer matching video codecs
var videoCodecs = ContainerProfile.SplitValue(videoCodec);
- var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream.Codec) ? videoStream.Codec : null;
- playlistItem.VideoCodecs = directVideoCodec != null ? new[] { directVideoCodec } : videoCodecs;
+ var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream?.Codec) ? videoStream?.Codec : null;
+ if (directVideoCodec != null)
+ {
+ // merge directVideoCodec to videoCodecs
+ Array.Resize(ref videoCodecs, videoCodecs.Length + 1);
+ videoCodecs[^1] = directVideoCodec;
+ }
+
+ playlistItem.VideoCodecs = videoCodecs;
- // copy video codec options as a starting point, this applies to transcode and direct-stream
- playlistItem.MaxFramerate = videoStream.AverageFrameRate;
- var qualifier = videoStream.Codec;
- if (videoStream.Level.HasValue)
+ // Copy video codec options as a starting point, this applies to transcode and direct-stream
+ playlistItem.MaxFramerate = videoStream?.AverageFrameRate;
+ var qualifier = videoStream?.Codec;
+ if (videoStream?.Level != null)
{
playlistItem.SetOption(qualifier, "level", videoStream.Level.Value.ToString(CultureInfo.InvariantCulture));
}
- if (videoStream.BitDepth.HasValue)
+ if (videoStream?.BitDepth != null)
{
playlistItem.SetOption(qualifier, "videobitdepth", videoStream.BitDepth.Value.ToString(CultureInfo.InvariantCulture));
}
- if (!string.IsNullOrEmpty(videoStream.Profile))
+ if (!string.IsNullOrEmpty(videoStream?.Profile))
{
playlistItem.SetOption(qualifier, "profile", videoStream.Profile.ToLowerInvariant());
}
- if (videoStream.Level != 0)
+ if (videoStream != null && videoStream.Level != 0)
{
playlistItem.SetOption(qualifier, "level", videoStream.Level.ToString());
}
- // prefer matching audio codecs, could do better here
+ // Prefer matching audio codecs, could do better here
var audioCodecs = ContainerProfile.SplitValue(audioCodec);
var directAudioStream = candidateAudioStreams.FirstOrDefault(stream => ContainerProfile.ContainsContainer(audioCodecs, stream.Codec));
playlistItem.AudioCodecs = audioCodecs;
@@ -806,7 +813,7 @@ namespace MediaBrowser.Model.Dlna
playlistItem.AudioStreamIndex = audioStream.Index;
playlistItem.AudioCodecs = new[] { audioStream.Codec };
- // copy matching audio codec options
+ // Copy matching audio codec options
playlistItem.AudioSampleRate = audioStream.SampleRate;
playlistItem.SetOption(qualifier, "audiochannels", audioStream.Channels.ToString());
@@ -827,6 +834,7 @@ namespace MediaBrowser.Model.Dlna
int? videoBitrate = videoStream?.BitRate;
double? videoLevel = videoStream?.Level;
string videoProfile = videoStream?.Profile;
+ string videoRangeType = videoStream?.VideoRangeType;
float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
bool? isAnamorphic = videoStream?.IsAnamorphic;
bool? isInterlaced = videoStream?.IsInterlaced;
@@ -843,7 +851,7 @@ namespace MediaBrowser.Model.Dlna
var appliedVideoConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.Video &&
i.ContainsAnyCodec(videoCodec, container) &&
- i.ApplyConditions.Any(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)));
+ i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)));
var isFirstAppliedCodecProfile = true;
foreach (var i in appliedVideoConditions)
{
@@ -873,9 +881,9 @@ namespace MediaBrowser.Model.Dlna
int? inputAudioBitDepth = audioStream == null ? null : audioStream.BitDepth;
var appliedAudioConditions = options.Profile.CodecProfiles
- .Where(i => i.Type == CodecType.Video &&
+ .Where(i => i.Type == CodecType.VideoAudio &&
i.ContainsAnyCodec(audioCodec, container) &&
- i.ApplyConditions.Any(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio)));
+ i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio)));
isFirstAppliedCodecProfile = true;
foreach (var i in appliedAudioConditions)
{
@@ -1067,19 +1075,20 @@ namespace MediaBrowser.Model.Dlna
DeviceProfile profile = options.Profile;
string container = mediaSource.Container;
- // video
+ // Video
int? width = videoStream?.Width;
int? height = videoStream?.Height;
int? bitDepth = videoStream?.BitDepth;
int? videoBitrate = videoStream?.BitRate;
double? videoLevel = videoStream?.Level;
string videoProfile = videoStream?.Profile;
+ string videoRangeType = videoStream?.VideoRangeType;
float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
bool? isAnamorphic = videoStream?.IsAnamorphic;
bool? isInterlaced = videoStream?.IsInterlaced;
string videoCodecTag = videoStream?.CodecTag;
bool? isAvc = videoStream?.IsAVC;
- // audio
+ // Audio
var defaultLanguage = audioStream?.Language ?? string.Empty;
var defaultMarked = audioStream?.IsDefault ?? false;
@@ -1091,7 +1100,7 @@ namespace MediaBrowser.Model.Dlna
int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video);
var checkVideoConditions = (ProfileCondition[] conditions) =>
- conditions.Where(applyCondition => !ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc));
+ conditions.Where(applyCondition => !ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc));
// Check container conditions
var containerProfileReasons = AggregateFailureConditions(
@@ -1108,18 +1117,9 @@ namespace MediaBrowser.Model.Dlna
profile,
"VideoCodecProfile",
profile.CodecProfiles
- .Where(codecProfile => codecProfile.Type == CodecType.Video && codecProfile.ContainsAnyCodec(videoStream?.Codec, container))
- .SelectMany(codecProfile =>
- {
- var failedApplyConditions = checkVideoConditions(codecProfile.ApplyConditions);
- if (!failedApplyConditions.Any())
- {
- return Array.Empty<ProfileCondition>();
- }
-
- var failedConditions = checkVideoConditions(codecProfile.Conditions);
- return failedApplyConditions.Concat(failedConditions);
- }));
+ .Where(codecProfile => codecProfile.Type == CodecType.Video && codecProfile.ContainsAnyCodec(videoStream?.Codec, container) &&
+ !checkVideoConditions(codecProfile.ApplyConditions).Any())
+ .SelectMany(codecProfile => checkVideoConditions(codecProfile.Conditions)));
// Check audiocandidates profile conditions
var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream, defaultLanguage, defaultMarked));
@@ -1189,7 +1189,18 @@ namespace MediaBrowser.Model.Dlna
audioCodecProfileReasons = audioStreamMatches.GetValueOrDefault(selectedAudioStream);
}
- var failureReasons = directPlayProfileReasons | containerProfileReasons | videoCodecProfileReasons | audioCodecProfileReasons | subtitleProfileReasons;
+ var failureReasons = directPlayProfileReasons | containerProfileReasons | subtitleProfileReasons;
+
+ if ((failureReasons & TranscodeReason.VideoCodecNotSupported) == 0)
+ {
+ failureReasons |= videoCodecProfileReasons;
+ }
+
+ if ((failureReasons & TranscodeReason.AudioCodecNotSupported) == 0)
+ {
+ failureReasons |= audioCodecProfileReasons;
+ }
+
var directStreamFailureReasons = failureReasons & (~DirectStreamReasons);
PlayMethod? playMethod = null;
@@ -1206,6 +1217,7 @@ namespace MediaBrowser.Model.Dlna
return (Result: (Profile: directPlayProfile, PlayMethod: playMethod, AudioStreamIndex: selectedAudioStream?.Index, TranscodeReason: failureReasons), Order: order, Rank: ranked);
})
.OrderByDescending(analysis => analysis.Result.PlayMethod)
+ .ThenByDescending(analysis => analysis.Rank)
.ThenBy(analysis => analysis.Order)
.ToArray()
.ToLookup(analysis => analysis.Result.PlayMethod != null);
@@ -1218,7 +1230,7 @@ namespace MediaBrowser.Model.Dlna
return profileMatch;
}
- var failureReasons = analyzedProfiles[false].OrderBy(a => a.Result.TranscodeReason).ThenBy(analysis => analysis.Order).FirstOrDefault().Result.TranscodeReason;
+ var failureReasons = analyzedProfiles[false].Select(analysis => analysis.Result).FirstOrDefault().TranscodeReason;
if (failureReasons == 0)
{
failureReasons = TranscodeReason.DirectPlayError;
@@ -1264,13 +1276,13 @@ namespace MediaBrowser.Model.Dlna
mediaSource.Path ?? "Unknown path");
}
- private TranscodeReason IsEligibleForDirectPlay(
+ private TranscodeReason IsBitrateEligibleForDirectPlayback(
MediaSourceInfo item,
long maxBitrate,
VideoOptions options,
PlayMethod playMethod)
{
- bool result = IsItemBitrateEligibleForDirectPlay(item, maxBitrate, playMethod);
+ bool result = IsItemBitrateEligibleForDirectPlayback(item, maxBitrate, playMethod);
if (!result)
{
return TranscodeReason.ContainerBitrateExceedsLimit;
@@ -1438,7 +1450,7 @@ namespace MediaBrowser.Model.Dlna
return null;
}
- private bool IsItemBitrateEligibleForDirectPlay(MediaSourceInfo item, long maxBitrate, PlayMethod playMethod)
+ private bool IsItemBitrateEligibleForDirectPlayback(MediaSourceInfo item, long maxBitrate, PlayMethod playMethod)
{
// Don't restrict by bitrate if coming from an external domain
if (item.IsRemote)
@@ -1842,6 +1854,38 @@ namespace MediaBrowser.Model.Dlna
break;
}
+ case ProfileConditionValue.VideoRangeType:
+ {
+ if (string.IsNullOrEmpty(qualifier))
+ {
+ continue;
+ }
+
+ // change from split by | to comma
+ // strip spaces to avoid having to encode
+ var values = value
+ .Split('|', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+
+ if (condition.Condition == ProfileConditionType.Equals)
+ {
+ item.SetOption(qualifier, "rangetype", string.Join(',', values));
+ }
+ else if (condition.Condition == ProfileConditionType.EqualsAny)
+ {
+ var currentValue = item.GetOption(qualifier, "rangetype");
+ if (!string.IsNullOrEmpty(currentValue) && values.Any(v => string.Equals(v, currentValue, StringComparison.OrdinalIgnoreCase)))
+ {
+ item.SetOption(qualifier, "rangetype", currentValue);
+ }
+ else
+ {
+ item.SetOption(qualifier, "rangetype", string.Join(',', values));
+ }
+ }
+
+ break;
+ }
+
case ProfileConditionValue.Height:
{
if (!enableNonQualifiedConditions)
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index da089602f..5cfa2e7e3 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
-using System.Linq;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@@ -281,6 +280,29 @@ namespace MediaBrowser.Model.Dlna
}
/// <summary>
+ /// Gets the target video range type that will be in the output stream.
+ /// </summary>
+ public string TargetVideoRangeType
+ {
+ get
+ {
+ if (IsDirectStream)
+ {
+ return TargetVideoStream?.VideoRangeType;
+ }
+
+ var targetVideoCodecs = TargetVideoCodec;
+ var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
+ if (!string.IsNullOrEmpty(videoCodec))
+ {
+ return GetOption(videoCodec, "rangetype");
+ }
+
+ return TargetVideoStream?.VideoRangeType;
+ }
+ }
+
+ /// <summary>
/// Gets the target video codec tag.
/// </summary>
/// <value>The target video codec tag.</value>
diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs
index 094dc73b2..fdb84fa32 100644
--- a/MediaBrowser.Model/Dto/BaseItemDto.cs
+++ b/MediaBrowser.Model/Dto/BaseItemDto.cs
@@ -294,13 +294,13 @@ namespace MediaBrowser.Model.Dto
public NameGuidPair[] GenreItems { get; set; }
/// <summary>
- /// Gets or sets wether the item has a logo, this will hold the Id of the Parent that has one.
+ /// Gets or sets whether the item has a logo, this will hold the Id of the Parent that has one.
/// </summary>
/// <value>The parent logo item id.</value>
public Guid? ParentLogoItemId { get; set; }
/// <summary>
- /// Gets or sets wether the item has any backdrops, this will hold the Id of the Parent that has one.
+ /// Gets or sets whether the item has any backdrops, this will hold the Id of the Parent that has one.
/// </summary>
/// <value>The parent backdrop item id.</value>
public Guid? ParentBackdropItemId { get; set; }
@@ -506,7 +506,7 @@ namespace MediaBrowser.Model.Dto
public string ParentLogoImageTag { get; set; }
/// <summary>
- /// Gets or sets wether the item has fan art, this will hold the Id of the Parent that has one.
+ /// Gets or sets whether the item has fan art, this will hold the Id of the Parent that has one.
/// </summary>
/// <value>The parent art item id.</value>
public Guid? ParentArtItemId { get; set; }
diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs
index 4742d21e9..90a60cf47 100644
--- a/MediaBrowser.Model/Entities/MediaStream.cs
+++ b/MediaBrowser.Model/Entities/MediaStream.cs
@@ -73,6 +73,54 @@ namespace MediaBrowser.Model.Entities
public string ColorPrimaries { get; set; }
/// <summary>
+ /// Gets or sets the Dolby Vision version major.
+ /// </summary>
+ /// <value>The Dolby Vision version major.</value>
+ public int? DvVersionMajor { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Dolby Vision version minor.
+ /// </summary>
+ /// <value>The Dolby Vision version minor.</value>
+ public int? DvVersionMinor { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Dolby Vision profile.
+ /// </summary>
+ /// <value>The Dolby Vision profile.</value>
+ public int? DvProfile { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Dolby Vision level.
+ /// </summary>
+ /// <value>The Dolby Vision level.</value>
+ public int? DvLevel { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Dolby Vision rpu present flag.
+ /// </summary>
+ /// <value>The Dolby Vision rpu present flag.</value>
+ public int? RpuPresentFlag { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Dolby Vision el present flag.
+ /// </summary>
+ /// <value>The Dolby Vision el present flag.</value>
+ public int? ElPresentFlag { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Dolby Vision bl present flag.
+ /// </summary>
+ /// <value>The Dolby Vision bl present flag.</value>
+ public int? BlPresentFlag { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Dolby Vision bl signal compatibility id.
+ /// </summary>
+ /// <value>The Dolby Vision bl signal compatibility id.</value>
+ public int? DvBlSignalCompatibilityId { get; set; }
+
+ /// <summary>
/// Gets or sets the comment.
/// </summary>
/// <value>The comment.</value>
@@ -104,33 +152,64 @@ namespace MediaBrowser.Model.Entities
{
get
{
- if (Type != MediaStreamType.Video)
- {
- return null;
- }
+ var (videoRange, _) = GetVideoColorRange();
- var colorTransfer = ColorTransfer;
-
- if (string.Equals(colorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
- || string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
- {
- return "HDR";
- }
+ return videoRange;
+ }
+ }
- // For some Dolby Vision files, no color transfer is provided, so check the codec
+ /// <summary>
+ /// Gets the video range type.
+ /// </summary>
+ /// <value>The video range type.</value>
+ public string VideoRangeType
+ {
+ get
+ {
+ var (_, videoRangeType) = GetVideoColorRange();
- var codecTag = CodecTag;
+ return videoRangeType;
+ }
+ }
- if (string.Equals(codecTag, "dva1", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codecTag, "dvav", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codecTag, "dvh1", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codecTag, "dvhe", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codecTag, "dav1", StringComparison.OrdinalIgnoreCase))
+ /// <summary>
+ /// Gets the video dovi title.
+ /// </summary>
+ /// <value>The video dovi title.</value>
+ public string VideoDoViTitle
+ {
+ get
+ {
+ var dvProfile = DvProfile;
+ var rpuPresentFlag = RpuPresentFlag == 1;
+ var blPresentFlag = BlPresentFlag == 1;
+ var dvBlCompatId = DvBlSignalCompatibilityId;
+
+ if (rpuPresentFlag
+ && blPresentFlag
+ && (dvProfile == 4
+ || dvProfile == 5
+ || dvProfile == 7
+ || dvProfile == 8
+ || dvProfile == 9))
{
- return "HDR";
+ var title = "DV Profile " + dvProfile;
+
+ if (dvBlCompatId > 0)
+ {
+ title += "." + dvBlCompatId;
+ }
+
+ return dvBlCompatId switch
+ {
+ 1 => title + " (HDR10)",
+ 2 => title + " (SDR)",
+ 4 => title + " (HLG)",
+ _ => title
+ };
}
- return "SDR";
+ return null;
}
}
@@ -509,15 +588,26 @@ namespace MediaBrowser.Model.Entities
return Width switch
{
- <= 720 when Height <= 480 => IsInterlaced ? "480i" : "480p",
- // 720x576 (PAL) (768 when rescaled for square pixels)
- <= 768 when Height <= 576 => IsInterlaced ? "576i" : "576p",
- // 960x540 (sometimes 544 which is multiple of 16)
+ // 256x144 (16:9 square pixel format)
+ <= 256 when Height <= 144 => IsInterlaced ? "144i" : "144p",
+ // 426x240 (16:9 square pixel format)
+ <= 426 when Height <= 240 => IsInterlaced ? "240i" : "240p",
+ // 640x360 (16:9 square pixel format)
+ <= 640 when Height <= 360 => IsInterlaced ? "360i" : "360p",
+ // 682x384 (16:9 square pixel format)
+ <= 682 when Height <= 384 => IsInterlaced ? "384i" : "384p",
+ // 720x404 (16:9 square pixel format)
+ <= 720 when Height <= 404 => IsInterlaced ? "404i" : "404p",
+ // 854x480 (16:9 square pixel format)
+ <= 854 when Height <= 480 => IsInterlaced ? "480i" : "480p",
+ // 960x544 (16:9 square pixel format)
<= 960 when Height <= 544 => IsInterlaced ? "540i" : "540p",
+ // 1024x576 (16:9 square pixel format)
+ <= 1024 when Height <= 576 => IsInterlaced ? "576i" : "576p",
// 1280x720
<= 1280 when Height <= 962 => IsInterlaced ? "720i" : "720p",
- // 1920x1080
- <= 1920 when Height <= 1440 => IsInterlaced ? "1080i" : "1080p",
+ // 2560x1080 (FHD ultra wide 21:9) using 1440px width to accommodate WQHD
+ <= 2560 when Height <= 1440 => IsInterlaced ? "1080i" : "1080p",
// 4K
<= 4096 when Height <= 3072 => "4K",
// 8K
@@ -572,5 +662,45 @@ namespace MediaBrowser.Model.Entities
return true;
}
+
+ public (string VideoRange, string VideoRangeType) GetVideoColorRange()
+ {
+ if (Type != MediaStreamType.Video)
+ {
+ return (null, null);
+ }
+
+ var colorTransfer = ColorTransfer;
+
+ if (string.Equals(colorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase))
+ {
+ return ("HDR", "HDR10");
+ }
+
+ if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
+ {
+ return ("HDR", "HLG");
+ }
+
+ var codecTag = CodecTag;
+ var dvProfile = DvProfile;
+ var rpuPresentFlag = RpuPresentFlag == 1;
+ var blPresentFlag = BlPresentFlag == 1;
+ var dvBlCompatId = DvBlSignalCompatibilityId;
+
+ var isDoViHDRProfile = dvProfile == 5 || dvProfile == 7 || dvProfile == 8;
+ var isDoViHDRFlag = rpuPresentFlag && blPresentFlag && (dvBlCompatId == 0 || dvBlCompatId == 1 || dvBlCompatId == 4);
+
+ if ((isDoViHDRProfile && isDoViHDRFlag)
+ || string.Equals(codecTag, "dovi", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codecTag, "dvh1", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codecTag, "dvhe", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codecTag, "dav1", StringComparison.OrdinalIgnoreCase))
+ {
+ return ("HDR", "DOVI");
+ }
+
+ return ("SDR", "SDR");
+ }
}
}
diff --git a/MediaBrowser.Model/Entities/MediaStreamType.cs b/MediaBrowser.Model/Entities/MediaStreamType.cs
index e09aaf6d0..83751a6a7 100644
--- a/MediaBrowser.Model/Entities/MediaStreamType.cs
+++ b/MediaBrowser.Model/Entities/MediaStreamType.cs
@@ -23,6 +23,11 @@ namespace MediaBrowser.Model.Entities
/// <summary>
/// The embedded image.
/// </summary>
- EmbeddedImage
+ EmbeddedImage,
+
+ /// <summary>
+ /// The data.
+ /// </summary>
+ Data
}
}
diff --git a/MediaBrowser.Model/Entities/MetadataProvider.cs b/MediaBrowser.Model/Entities/MetadataProvider.cs
index e9c098021..37e3d8864 100644
--- a/MediaBrowser.Model/Entities/MetadataProvider.cs
+++ b/MediaBrowser.Model/Entities/MetadataProvider.cs
@@ -8,6 +8,12 @@ namespace MediaBrowser.Model.Entities
public enum MetadataProvider
{
/// <summary>
+ /// This metadata provider is for users and/or plugins to override the
+ /// default merging behaviour.
+ /// </summary>
+ Custom = 0,
+
+ /// <summary>
/// The imdb.
/// </summary>
Imdb = 2,
diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs
index ce4b0ec92..d3b8400f3 100644
--- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs
+++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+using System.Linq;
namespace MediaBrowser.Model.Entities
{
@@ -10,6 +11,16 @@ namespace MediaBrowser.Model.Entities
public static class ProviderIdsExtensions
{
/// <summary>
+ /// Case insensitive dictionary of <see cref="MetadataProvider"/> string representation.
+ /// </summary>
+ private static readonly Dictionary<string, string> _metadataProviderEnumDictionary =
+ Enum.GetValues<MetadataProvider>()
+ .ToDictionary(
+ enumValue => enumValue.ToString(),
+ enumValue => enumValue.ToString(),
+ StringComparer.OrdinalIgnoreCase);
+
+ /// <summary>
/// Checks if this instance has an id for the given provider.
/// </summary>
/// <param name="instance">The instance.</param>
@@ -17,10 +28,7 @@ namespace MediaBrowser.Model.Entities
/// <returns><c>true</c> if a provider id with the given name was found; otherwise <c>false</c>.</returns>
public static bool HasProviderId(this IHasProviderIds instance, string name)
{
- if (instance == null)
- {
- throw new ArgumentNullException(nameof(instance));
- }
+ ArgumentNullException.ThrowIfNull(instance);
return instance.TryGetProviderId(name, out _);
}
@@ -45,10 +53,7 @@ namespace MediaBrowser.Model.Entities
/// <returns><c>true</c> if a provider id with the given name was found; otherwise <c>false</c>.</returns>
public static bool TryGetProviderId(this IHasProviderIds instance, string name, [NotNullWhen(true)] out string? id)
{
- if (instance == null)
- {
- throw new ArgumentNullException(nameof(instance));
- }
+ ArgumentNullException.ThrowIfNull(instance);
if (instance.ProviderIds == null)
{
@@ -108,12 +113,9 @@ namespace MediaBrowser.Model.Entities
/// <param name="instance">The instance.</param>
/// <param name="name">The name.</param>
/// <param name="value">The value.</param>
- public static void SetProviderId(this IHasProviderIds instance, string name, string value)
+ public static void SetProviderId(this IHasProviderIds instance, string name, string? value)
{
- if (instance == null)
- {
- throw new ArgumentNullException(nameof(instance));
- }
+ ArgumentNullException.ThrowIfNull(instance);
// If it's null remove the key from the dictionary
if (string.IsNullOrEmpty(value))
@@ -125,7 +127,15 @@ namespace MediaBrowser.Model.Entities
// Ensure it exists
instance.ProviderIds ??= new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- instance.ProviderIds[name] = value;
+ // Match on internal MetadataProvider enum string values before adding arbitrary providers
+ if (_metadataProviderEnumDictionary.TryGetValue(name, out var enumValue))
+ {
+ instance.ProviderIds[enumValue] = value;
+ }
+ else
+ {
+ instance.ProviderIds[name] = value;
+ }
}
}
diff --git a/MediaBrowser.Model/LiveTv/TunerHostInfo.cs b/MediaBrowser.Model/LiveTv/TunerHostInfo.cs
index 05576a0f8..a832169c2 100644
--- a/MediaBrowser.Model/LiveTv/TunerHostInfo.cs
+++ b/MediaBrowser.Model/LiveTv/TunerHostInfo.cs
@@ -8,6 +8,7 @@ namespace MediaBrowser.Model.LiveTv
public TunerHostInfo()
{
AllowHWTranscoding = true;
+ IgnoreDts = true;
}
public string Id { get; set; }
@@ -31,5 +32,7 @@ namespace MediaBrowser.Model.LiveTv
public int TunerCount { get; set; }
public string UserAgent { get; set; }
+
+ public bool IgnoreDts { get; set; }
}
}
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index 43b465b29..ad2ff1ba2 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Model</PackageId>
- <VersionPrefix>10.8.0</VersionPrefix>
+ <VersionPrefix>10.9.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
@@ -34,13 +34,13 @@
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
- <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
- <PackageReference Include="MimeTypes" Version="2.3.0">
+ <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.2" />
+ <PackageReference Include="MimeTypes" Version="2.4.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Globalization" Version="4.3.0" />
- <PackageReference Include="System.Text.Json" Version="6.0.2" />
+ <PackageReference Include="System.Text.Json" Version="6.0.6" />
</ItemGroup>
<ItemGroup>
@@ -54,7 +54,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
diff --git a/MediaBrowser.Model/MediaInfo/SubtitleFormat.cs b/MediaBrowser.Model/MediaInfo/SubtitleFormat.cs
index 9bc5c31f6..85de91694 100644
--- a/MediaBrowser.Model/MediaInfo/SubtitleFormat.cs
+++ b/MediaBrowser.Model/MediaInfo/SubtitleFormat.cs
@@ -5,6 +5,7 @@ namespace MediaBrowser.Model.MediaInfo
public static class SubtitleFormat
{
public const string SRT = "srt";
+ public const string SUBRIP = "subrip";
public const string SSA = "ssa";
public const string ASS = "ass";
public const string VTT = "vtt";
diff --git a/MediaBrowser.Model/Querying/ItemSortBy.cs b/MediaBrowser.Model/Querying/ItemSortBy.cs
index 0a28acf37..470507c53 100644
--- a/MediaBrowser.Model/Querying/ItemSortBy.cs
+++ b/MediaBrowser.Model/Querying/ItemSortBy.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
namespace MediaBrowser.Model.Querying
{
/// <summary>
@@ -7,6 +5,9 @@ namespace MediaBrowser.Model.Querying
/// </summary>
public static class ItemSortBy
{
+ /// <summary>
+ /// The aired episode order.
+ /// </summary>
public const string AiredEpisodeOrder = "AiredEpisodeOrder";
/// <summary>
@@ -44,6 +45,9 @@ namespace MediaBrowser.Model.Querying
/// </summary>
public const string PremiereDate = "PremiereDate";
+ /// <summary>
+ /// The start date.
+ /// </summary>
public const string StartDate = "StartDate";
/// <summary>
@@ -51,6 +55,9 @@ namespace MediaBrowser.Model.Querying
/// </summary>
public const string SortName = "SortName";
+ /// <summary>
+ /// The name.
+ /// </summary>
public const string Name = "Name";
/// <summary>
@@ -83,28 +90,69 @@ namespace MediaBrowser.Model.Querying
/// </summary>
public const string CriticRating = "CriticRating";
+ /// <summary>
+ /// The IsFolder boolean.
+ /// </summary>
public const string IsFolder = "IsFolder";
+ /// <summary>
+ /// The IsUnplayed boolean.
+ /// </summary>
public const string IsUnplayed = "IsUnplayed";
+ /// <summary>
+ /// The IsPlayed boolean.
+ /// </summary>
public const string IsPlayed = "IsPlayed";
+ /// <summary>
+ /// The series sort.
+ /// </summary>
public const string SeriesSortName = "SeriesSortName";
+ /// <summary>
+ /// The video bitrate.
+ /// </summary>
public const string VideoBitRate = "VideoBitRate";
+ /// <summary>
+ /// The air time.
+ /// </summary>
public const string AirTime = "AirTime";
+ /// <summary>
+ /// The studio.
+ /// </summary>
public const string Studio = "Studio";
+ /// <summary>
+ /// The IsFavouriteOrLiked boolean.
+ /// </summary>
public const string IsFavoriteOrLiked = "IsFavoriteOrLiked";
+ /// <summary>
+ /// The last content added date.
+ /// </summary>
public const string DateLastContentAdded = "DateLastContentAdded";
+ /// <summary>
+ /// The series last played date.
+ /// </summary>
public const string SeriesDatePlayed = "SeriesDatePlayed";
+ /// <summary>
+ /// The parent index number.
+ /// </summary>
public const string ParentIndexNumber = "ParentIndexNumber";
+ /// <summary>
+ /// The index number.
+ /// </summary>
public const string IndexNumber = "IndexNumber";
+
+ /// <summary>
+ /// The similarity score.
+ /// </summary>
+ public const string SimilarityScore = "SimilarityScore";
}
}
diff --git a/MediaBrowser.Model/Querying/NextUpQuery.cs b/MediaBrowser.Model/Querying/NextUpQuery.cs
index 133d6a916..0fb996df9 100644
--- a/MediaBrowser.Model/Querying/NextUpQuery.cs
+++ b/MediaBrowser.Model/Querying/NextUpQuery.cs
@@ -33,7 +33,7 @@ namespace MediaBrowser.Model.Querying
/// Gets or sets the series id.
/// </summary>
/// <value>The series id.</value>
- public string SeriesId { get; set; }
+ public Guid? SeriesId { get; set; }
/// <summary>
/// Gets or sets the start index. Use for paging.
diff --git a/MediaBrowser.Model/Search/SearchHint.cs b/MediaBrowser.Model/Search/SearchHint.cs
index 983dbd2bc..3fa7f3d56 100644
--- a/MediaBrowser.Model/Search/SearchHint.cs
+++ b/MediaBrowser.Model/Search/SearchHint.cs
@@ -1,8 +1,6 @@
-#nullable disable
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
+using Jellyfin.Data.Enums;
namespace MediaBrowser.Model.Search
{
@@ -12,11 +10,27 @@ namespace MediaBrowser.Model.Search
public class SearchHint
{
/// <summary>
+ /// Initializes a new instance of the <see cref="SearchHint" /> class.
+ /// </summary>
+ public SearchHint()
+ {
+ Name = string.Empty;
+ MatchedTerm = string.Empty;
+ MediaType = string.Empty;
+ Artists = Array.Empty<string>();
+ }
+
+ /// <summary>
/// Gets or sets the item id.
/// </summary>
/// <value>The item id.</value>
+ [Obsolete("Use Id instead")]
public Guid ItemId { get; set; }
+ /// <summary>
+ /// Gets or sets the item id.
+ /// </summary>
+ /// <value>The item id.</value>
public Guid Id { get; set; }
/// <summary>
@@ -53,38 +67,42 @@ namespace MediaBrowser.Model.Search
/// Gets or sets the image tag.
/// </summary>
/// <value>The image tag.</value>
- public string PrimaryImageTag { get; set; }
+ public string? PrimaryImageTag { get; set; }
/// <summary>
/// Gets or sets the thumb image tag.
/// </summary>
/// <value>The thumb image tag.</value>
- public string ThumbImageTag { get; set; }
+ public string? ThumbImageTag { get; set; }
/// <summary>
/// Gets or sets the thumb image item identifier.
/// </summary>
/// <value>The thumb image item identifier.</value>
- public string ThumbImageItemId { get; set; }
+ public string? ThumbImageItemId { get; set; }
/// <summary>
/// Gets or sets the backdrop image tag.
/// </summary>
/// <value>The backdrop image tag.</value>
- public string BackdropImageTag { get; set; }
+ public string? BackdropImageTag { get; set; }
/// <summary>
/// Gets or sets the backdrop image item identifier.
/// </summary>
/// <value>The backdrop image item identifier.</value>
- public string BackdropImageItemId { get; set; }
+ public string? BackdropImageItemId { get; set; }
/// <summary>
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
- public string Type { get; set; }
+ public BaseItemKind Type { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is folder.
+ /// </summary>
+ /// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value>
public bool? IsFolder { get; set; }
/// <summary>
@@ -99,31 +117,47 @@ namespace MediaBrowser.Model.Search
/// <value>The type of the media.</value>
public string MediaType { get; set; }
+ /// <summary>
+ /// Gets or sets the start date.
+ /// </summary>
+ /// <value>The start date.</value>
public DateTime? StartDate { get; set; }
+ /// <summary>
+ /// Gets or sets the end date.
+ /// </summary>
+ /// <value>The end date.</value>
public DateTime? EndDate { get; set; }
/// <summary>
/// Gets or sets the series.
/// </summary>
/// <value>The series.</value>
- public string Series { get; set; }
+ public string? Series { get; set; }
- public string Status { get; set; }
+ /// <summary>
+ /// Gets or sets the status.
+ /// </summary>
+ /// <value>The status.</value>
+ public string? Status { get; set; }
/// <summary>
/// Gets or sets the album.
/// </summary>
/// <value>The album.</value>
- public string Album { get; set; }
+ public string? Album { get; set; }
- public Guid AlbumId { get; set; }
+ /// <summary>
+ /// Gets or sets the album id.
+ /// </summary>
+ /// <value>The album id.</value>
+ public Guid? AlbumId { get; set; }
/// <summary>
/// Gets or sets the album artist.
/// </summary>
/// <value>The album artist.</value>
- public string AlbumArtist { get; set; }
+ public string? AlbumArtist { get; set; }
/// <summary>
/// Gets or sets the artists.
@@ -147,13 +181,13 @@ namespace MediaBrowser.Model.Search
/// Gets or sets the channel identifier.
/// </summary>
/// <value>The channel identifier.</value>
- public Guid ChannelId { get; set; }
+ public Guid? ChannelId { get; set; }
/// <summary>
/// Gets or sets the name of the channel.
/// </summary>
/// <value>The name of the channel.</value>
- public string ChannelName { get; set; }
+ public string? ChannelName { get; set; }
/// <summary>
/// Gets or sets the primary image aspect ratio.
diff --git a/MediaBrowser.Model/Session/GeneralCommand.cs b/MediaBrowser.Model/Session/GeneralCommand.cs
index 757b19b31..dfbb616aa 100644
--- a/MediaBrowser.Model/Session/GeneralCommand.cs
+++ b/MediaBrowser.Model/Session/GeneralCommand.cs
@@ -14,9 +14,9 @@ public class GeneralCommand
}
[JsonConstructor]
- public GeneralCommand(Dictionary<string, string> arguments)
+ public GeneralCommand(Dictionary<string, string>? arguments)
{
- Arguments = arguments;
+ Arguments = arguments ?? new Dictionary<string, string>();
}
public GeneralCommandType Name { get; set; }
diff --git a/MediaBrowser.Model/Session/PlayerStateInfo.cs b/MediaBrowser.Model/Session/PlayerStateInfo.cs
index 0f10605ea..80e6d4e0b 100644
--- a/MediaBrowser.Model/Session/PlayerStateInfo.cs
+++ b/MediaBrowser.Model/Session/PlayerStateInfo.cs
@@ -64,5 +64,11 @@ namespace MediaBrowser.Model.Session
/// </summary>
/// <value>The repeat mode.</value>
public RepeatMode RepeatMode { get; set; }
+
+ /// <summary>
+ /// Gets or sets the now playing live stream identifier.
+ /// </summary>
+ /// <value>The live stream identifier.</value>
+ public string LiveStreamId { get; set; }
}
}
diff --git a/MediaBrowser.Model/Session/TranscodeReason.cs b/MediaBrowser.Model/Session/TranscodeReason.cs
index 9da9f3323..bbdf4536b 100644
--- a/MediaBrowser.Model/Session/TranscodeReason.cs
+++ b/MediaBrowser.Model/Session/TranscodeReason.cs
@@ -17,6 +17,7 @@ namespace MediaBrowser.Model.Session
// Video Constraints
VideoProfileNotSupported = 1 << 6,
+ VideoRangeTypeNotSupported = 1 << 24,
VideoLevelNotSupported = 1 << 7,
VideoResolutionNotSupported = 1 << 8,
VideoBitDepthNotSupported = 1 << 9,
diff --git a/MediaBrowser.Model/SyncPlay/GroupStateType.cs b/MediaBrowser.Model/SyncPlay/GroupStateType.cs
index 7aa454f92..96364cacc 100644
--- a/MediaBrowser.Model/SyncPlay/GroupStateType.cs
+++ b/MediaBrowser.Model/SyncPlay/GroupStateType.cs
@@ -11,7 +11,7 @@ namespace MediaBrowser.Model.SyncPlay
Idle = 0,
/// <summary>
- /// The group is in wating state. Playback is paused. Will start playing when users are ready.
+ /// The group is in waiting state. Playback is paused. Will start playing when users are ready.
/// </summary>
Waiting = 1,
diff --git a/MediaBrowser.Model/Tasks/ITaskManager.cs b/MediaBrowser.Model/Tasks/ITaskManager.cs
index a86bf2a1c..13bebc479 100644
--- a/MediaBrowser.Model/Tasks/ITaskManager.cs
+++ b/MediaBrowser.Model/Tasks/ITaskManager.cs
@@ -22,7 +22,7 @@ namespace MediaBrowser.Model.Tasks
/// <summary>
/// Cancels if running and queue.
/// </summary>
- /// <typeparam name="T">An implementatin of <see cref="IScheduledTask" />.</typeparam>
+ /// <typeparam name="T">An implementation of <see cref="IScheduledTask" />.</typeparam>
/// <param name="options">Task options.</param>
void CancelIfRunningAndQueue<T>(TaskOptions options)
where T : IScheduledTask;
@@ -30,21 +30,21 @@ namespace MediaBrowser.Model.Tasks
/// <summary>
/// Cancels if running and queue.
/// </summary>
- /// <typeparam name="T">An implementatin of <see cref="IScheduledTask" />.</typeparam>
+ /// <typeparam name="T">An implementation of <see cref="IScheduledTask" />.</typeparam>
void CancelIfRunningAndQueue<T>()
where T : IScheduledTask;
/// <summary>
/// Cancels if running.
/// </summary>
- /// <typeparam name="T">An implementatin of <see cref="IScheduledTask" />.</typeparam>
+ /// <typeparam name="T">An implementation of <see cref="IScheduledTask" />.</typeparam>
void CancelIfRunning<T>()
where T : IScheduledTask;
/// <summary>
/// Queues the scheduled task.
/// </summary>
- /// <typeparam name="T">An implementatin of <see cref="IScheduledTask" />.</typeparam>
+ /// <typeparam name="T">An implementation of <see cref="IScheduledTask" />.</typeparam>
/// <param name="options">Task options.</param>
void QueueScheduledTask<T>(TaskOptions options)
where T : IScheduledTask;
@@ -52,7 +52,7 @@ namespace MediaBrowser.Model.Tasks
/// <summary>
/// Queues the scheduled task.
/// </summary>
- /// <typeparam name="T">An implementatin of <see cref="IScheduledTask" />.</typeparam>
+ /// <typeparam name="T">An implementation of <see cref="IScheduledTask" />.</typeparam>
void QueueScheduledTask<T>()
where T : IScheduledTask;
diff --git a/MediaBrowser.Model/Tasks/ITaskTrigger.cs b/MediaBrowser.Model/Tasks/ITaskTrigger.cs
index 8c3ec6626..0536f4ef7 100644
--- a/MediaBrowser.Model/Tasks/ITaskTrigger.cs
+++ b/MediaBrowser.Model/Tasks/ITaskTrigger.cs
@@ -21,10 +21,10 @@ namespace MediaBrowser.Model.Tasks
/// <summary>
/// Stars waiting for the trigger action.
/// </summary>
- /// <param name="lastResult">Result of the last run triggerd task.</param>
+ /// <param name="lastResult">Result of the last run triggered task.</param>
/// <param name="logger">The <see cref="ILogger"/>.</param>
/// <param name="taskName">The name of the task.</param>
- /// <param name="isApplicationStartup">Wheter or not this is is fired during startup.</param>
+ /// <param name="isApplicationStartup">Whether or not this is is fired during startup.</param>
void Start(TaskResult? lastResult, ILogger logger, string taskName, bool isApplicationStartup);
/// <summary>
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index 01e2a5db9..0f3f1bdcb 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -8,7 +8,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using Diacritics.Extensions;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@@ -655,8 +655,6 @@ namespace MediaBrowser.Providers.Manager
};
temp.Item.Path = item.Path;
- var userDataList = new List<UserItemData>();
-
// If replacing all metadata, run internet providers first
if (options.ReplaceAllMetadata)
{
@@ -670,7 +668,7 @@ namespace MediaBrowser.Providers.Manager
var hasLocalMetadata = false;
- foreach (var provider in providers.OfType<ILocalMetadataProvider<TItemType>>().ToList())
+ foreach (var provider in providers.OfType<ILocalMetadataProvider<TItemType>>())
{
var providerName = provider.GetType().Name;
Logger.LogDebug("Running {Provider} for {Item}", providerName, logName);
@@ -687,6 +685,11 @@ namespace MediaBrowser.Providers.Manager
{
try
{
+ if (!options.IsReplacingImage(remoteImage.Type))
+ {
+ continue;
+ }
+
await ProviderManager.SaveImage(item, remoteImage.Url, remoteImage.Type, null, cancellationToken).ConfigureAwait(false);
refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
}
@@ -701,11 +704,6 @@ namespace MediaBrowser.Providers.Manager
refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
}
- if (localItem.UserDataList != null)
- {
- userDataList.AddRange(localItem.UserDataList);
- }
-
MergeData(localItem, temp, Array.Empty<MetadataField>(), !options.ReplaceAllMetadata, true);
refreshResult.UpdateType |= ItemUpdateType.MetadataImport;
@@ -764,15 +762,11 @@ namespace MediaBrowser.Providers.Manager
}
}
- // var isUnidentified = failedProviderCount > 0 && successfulProviderCount == 0;
-
foreach (var provider in customProviders.Where(i => i is not IPreRefreshProvider))
{
await RunCustomProvider(provider, item, logName, options, refreshResult, cancellationToken).ConfigureAwait(false);
}
- // ImportUserData(item, userDataList, cancellationToken);
-
return refreshResult;
}
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index eb4bc3587..195c6afae 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -28,6 +28,7 @@ using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Net;
using MediaBrowser.Model.Providers;
using Microsoft.Extensions.Logging;
using Priority_Queue;
@@ -176,6 +177,12 @@ namespace MediaBrowser.Providers.Manager
throw new HttpRequestException("Invalid image received.", null, HttpStatusCode.NotFound);
}
+ // some iptv/epg providers don't correctly report media type, extract from url if no extension found
+ if (string.IsNullOrWhiteSpace(MimeTypes.ToExtension(contentType)))
+ {
+ contentType = MimeTypes.GetMimeType(url);
+ }
+
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
await SaveImage(
item,
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index 535f1b16f..9864db9ac 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -20,9 +20,9 @@
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
- <PackageReference Include="OptimizedPriorityQueue" Version="5.0.0" />
- <PackageReference Include="PlaylistsNET" Version="1.1.3" />
- <PackageReference Include="TMDbLib" Version="1.9.1" />
+ <PackageReference Include="OptimizedPriorityQueue" Version="5.1.0" />
+ <PackageReference Include="PlaylistsNET" Version="1.2.1" />
+ <PackageReference Include="TMDbLib" Version="1.9.2" />
</ItemGroup>
<PropertyGroup>
@@ -43,7 +43,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs
index 0bdf447ba..17164ee5c 100644
--- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs
+++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs
@@ -4,6 +4,7 @@ using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
+using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.MediaInfo
{
@@ -15,16 +16,19 @@ namespace MediaBrowser.Providers.MediaInfo
/// <summary>
/// Initializes a new instance of the <see cref="AudioResolver"/> class for external audio file processing.
/// </summary>
+ /// <param name="logger">The logger.</param>
/// <param name="localizationManager">The localization manager.</param>
/// <param name="mediaEncoder">The media encoder.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="namingOptions">The <see cref="NamingOptions"/> object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters.</param>
public AudioResolver(
+ ILogger<AudioResolver> logger,
ILocalizationManager localizationManager,
IMediaEncoder mediaEncoder,
IFileSystem fileSystem,
NamingOptions namingOptions)
: base(
+ logger,
localizationManager,
mediaEncoder,
fileSystem,
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
index fcd3f28d4..e58c0e281 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
@@ -47,7 +47,6 @@ namespace MediaBrowser.Providers.MediaInfo
private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None);
public FFProbeProvider(
- ILogger<FFProbeProvider> logger,
IMediaSourceManager mediaSourceManager,
IMediaEncoder mediaEncoder,
IItemRepository itemRepo,
@@ -59,11 +58,12 @@ namespace MediaBrowser.Providers.MediaInfo
IChapterManager chapterManager,
ILibraryManager libraryManager,
IFileSystem fileSystem,
+ ILoggerFactory loggerFactory,
NamingOptions namingOptions)
{
- _logger = logger;
- _audioResolver = new AudioResolver(localization, mediaEncoder, fileSystem, namingOptions);
- _subtitleResolver = new SubtitleResolver(localization, mediaEncoder, fileSystem, namingOptions);
+ _logger = loggerFactory.CreateLogger<FFProbeProvider>();
+ _audioResolver = new AudioResolver(loggerFactory.CreateLogger<AudioResolver>(), localization, mediaEncoder, fileSystem, namingOptions);
+ _subtitleResolver = new SubtitleResolver(loggerFactory.CreateLogger<SubtitleResolver>(), localization, mediaEncoder, fileSystem, namingOptions);
_videoProber = new FFProbeVideoInfo(
_logger,
mediaSourceManager,
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
index 4a289b3ab..8c08ab30e 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
@@ -173,16 +173,30 @@ namespace MediaBrowser.Providers.MediaInfo
IReadOnlyList<MediaAttachment> mediaAttachments;
ChapterInfo[] chapters;
+ mediaStreams = new List<MediaStream>();
+
+ // Add external streams before adding the streams from the file to preserve stream IDs on remote videos
+ await AddExternalSubtitlesAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
+
+ await AddExternalAudioAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
+
+ var startIndex = mediaStreams.Count == 0 ? 0 : (mediaStreams.Max(i => i.Index) + 1);
+
if (mediaInfo != null)
{
- mediaStreams = mediaInfo.MediaStreams.ToList();
+ foreach (var mediaStream in mediaInfo.MediaStreams)
+ {
+ mediaStream.Index = startIndex++;
+ mediaStreams.Add(mediaStream);
+ }
+
mediaAttachments = mediaInfo.MediaAttachments;
video.TotalBitrate = mediaInfo.Bitrate;
// 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
+ // For DVDs this may not always be accurate, so don't set the runtime if the item already has one
var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;
if (needToSetRuntime)
@@ -213,15 +227,20 @@ namespace MediaBrowser.Providers.MediaInfo
}
else
{
- mediaStreams = new List<MediaStream>();
+ var currentMediaStreams = video.GetMediaStreams();
+ foreach (var mediaStream in currentMediaStreams)
+ {
+ if (!mediaStream.IsExternal)
+ {
+ mediaStream.Index = startIndex++;
+ mediaStreams.Add(mediaStream);
+ }
+ }
+
mediaAttachments = Array.Empty<MediaAttachment>();
chapters = Array.Empty<ChapterInfo>();
}
- await AddExternalSubtitlesAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
-
- await AddExternalAudioAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
-
var libraryOptions = _libraryManager.GetLibraryOptions(video);
if (mediaInfo != null)
@@ -254,7 +273,11 @@ namespace MediaBrowser.Providers.MediaInfo
video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
_itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken);
- _itemRepo.SaveMediaAttachments(video.Id, mediaAttachments, cancellationToken);
+
+ if (mediaAttachments.Any())
+ {
+ _itemRepo.SaveMediaAttachments(video.Id, mediaAttachments, cancellationToken);
+ }
if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh ||
options.MetadataRefreshMode == MetadataRefreshMode.Default)
diff --git a/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs b/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs
index edb01824f..d55cc4491 100644
--- a/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs
+++ b/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
@@ -16,6 +15,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
+using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.MediaInfo
{
@@ -34,6 +34,7 @@ namespace MediaBrowser.Providers.MediaInfo
/// </summary>
private readonly IMediaEncoder _mediaEncoder;
+ private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
/// <summary>
@@ -49,18 +50,21 @@ namespace MediaBrowser.Providers.MediaInfo
/// <summary>
/// Initializes a new instance of the <see cref="MediaInfoResolver"/> class.
/// </summary>
+ /// <param name="logger">The logger.</param>
/// <param name="localizationManager">The localization manager.</param>
/// <param name="mediaEncoder">The media encoder.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="namingOptions">The <see cref="NamingOptions"/> object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters.</param>
/// <param name="type">The <see cref="DlnaProfileType"/> of the parsed file.</param>
protected MediaInfoResolver(
+ ILogger logger,
ILocalizationManager localizationManager,
IMediaEncoder mediaEncoder,
IFileSystem fileSystem,
NamingOptions namingOptions,
DlnaProfileType type)
{
+ _logger = logger;
_mediaEncoder = mediaEncoder;
_fileSystem = fileSystem;
_namingOptions = namingOptions;
@@ -102,26 +106,44 @@ namespace MediaBrowser.Providers.MediaInfo
{
if (!pathInfo.Path.AsSpan().EndsWith(".strm", StringComparison.OrdinalIgnoreCase))
{
- var mediaInfo = await GetMediaInfo(pathInfo.Path, _type, cancellationToken).ConfigureAwait(false);
-
- if (mediaInfo.MediaStreams.Count == 1)
+ try
{
- MediaStream mediaStream = mediaInfo.MediaStreams[0];
- mediaStream.Index = startIndex++;
- mediaStream.IsDefault = pathInfo.IsDefault || mediaStream.IsDefault;
- mediaStream.IsForced = pathInfo.IsForced || mediaStream.IsForced;
+ var mediaInfo = await GetMediaInfo(pathInfo.Path, _type, cancellationToken).ConfigureAwait(false);
- mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
- }
- else
- {
- foreach (MediaStream mediaStream in mediaInfo.MediaStreams)
+ if (mediaInfo.MediaStreams.Count == 1)
{
- mediaStream.Index = startIndex++;
+ MediaStream mediaStream = mediaInfo.MediaStreams[0];
- mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
+ if ((mediaStream.Type == MediaStreamType.Audio && _type == DlnaProfileType.Audio)
+ || (mediaStream.Type == MediaStreamType.Subtitle && _type == DlnaProfileType.Subtitle))
+ {
+ mediaStream.Index = startIndex++;
+ mediaStream.IsDefault = pathInfo.IsDefault || mediaStream.IsDefault;
+ mediaStream.IsForced = pathInfo.IsForced || mediaStream.IsForced;
+
+ mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
+ }
+ }
+ else
+ {
+ foreach (MediaStream mediaStream in mediaInfo.MediaStreams)
+ {
+ if ((mediaStream.Type == MediaStreamType.Audio && _type == DlnaProfileType.Audio)
+ || (mediaStream.Type == MediaStreamType.Subtitle && _type == DlnaProfileType.Subtitle))
+ {
+ mediaStream.Index = startIndex++;
+
+ mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
+ }
+ }
}
}
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error getting external streams from {Path}", pathInfo.Path);
+
+ continue;
+ }
}
}
@@ -153,6 +175,7 @@ namespace MediaBrowser.Providers.MediaInfo
}
var files = directoryService.GetFilePaths(folder, clearCache).ToList();
+ files.Remove(video.Path);
var internalMetadataPath = video.GetInternalMetadataPath();
if (_fileSystem.DirectoryExists(internalMetadataPath))
{
@@ -222,13 +245,6 @@ namespace MediaBrowser.Providers.MediaInfo
mediaStream.Title = string.IsNullOrEmpty(mediaStream.Title) ? (string.IsNullOrEmpty(pathInfo.Title) ? null : pathInfo.Title) : mediaStream.Title;
mediaStream.Language = string.IsNullOrEmpty(mediaStream.Language) ? (string.IsNullOrEmpty(pathInfo.Language) ? null : pathInfo.Language) : mediaStream.Language;
- mediaStream.Type = _type switch
- {
- DlnaProfileType.Audio => MediaStreamType.Audio,
- DlnaProfileType.Subtitle => MediaStreamType.Subtitle,
- _ => mediaStream.Type
- };
-
return mediaStream;
}
}
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
index 4b9ba944a..70e5bd783 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
@@ -4,6 +4,7 @@ using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
+using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.MediaInfo
{
@@ -15,16 +16,19 @@ namespace MediaBrowser.Providers.MediaInfo
/// <summary>
/// Initializes a new instance of the <see cref="SubtitleResolver"/> class for external subtitle file processing.
/// </summary>
+ /// <param name="logger">The logger.</param>
/// <param name="localizationManager">The localization manager.</param>
/// <param name="mediaEncoder">The media encoder.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="namingOptions">The <see cref="NamingOptions"/> object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters.</param>
public SubtitleResolver(
+ ILogger<SubtitleResolver> logger,
ILocalizationManager localizationManager,
IMediaEncoder mediaEncoder,
IFileSystem fileSystem,
NamingOptions namingOptions)
: base(
+ logger,
localizationManager,
mediaEncoder,
fileSystem,
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
index 5ae5ff3be..915fb97fd 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
@@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Music
/// <summary>
/// The Jellyfin user-agent is unrestricted but source IP must not exceed
/// one request per second, therefore we rate limit to avoid throttling.
- /// Be prudent, use a value slightly above the minimun required.
+ /// Be prudent, use a value slightly above the minimum required.
/// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting.
/// </summary>
private readonly long _musicBrainzQueryIntervalMs;
@@ -465,7 +465,8 @@ namespace MediaBrowser.Providers.Music
ValidationType = ValidationType.None,
CheckCharacters = false,
IgnoreProcessingInstructions = true,
- IgnoreComments = true
+ IgnoreComments = true,
+ Async = true
};
using var reader = XmlReader.Create(oReader, settings);
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
index 1feb7f4ea..906a42f36 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
@@ -13,7 +13,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
-using Diacritics.Extensions;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
diff --git a/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs
index fad989ab4..cb422ef3d 100644
--- a/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs
+++ b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs
@@ -2,7 +2,7 @@
using MediaBrowser.Model.Plugins;
-namespace MediaBrowser.Providers.Plugins.StudioImages
+namespace MediaBrowser.Providers.Plugins.StudioImages.Configuration
{
public class PluginConfiguration : BasePluginConfiguration
{
@@ -12,12 +12,19 @@ namespace MediaBrowser.Providers.Plugins.StudioImages
{
get
{
+ if (string.IsNullOrEmpty(_repository))
+ {
+ _repository = Plugin.DefaultServer;
+ }
+
return _repository;
}
set
{
- _repository = value.TrimEnd('/');
+ _repository = string.IsNullOrEmpty(value)
+ ? Plugin.DefaultServer
+ : value.TrimEnd('/');
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/StudioImages/Configuration/config.html b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/config.html
index f9fe3dc2e..63750dbcd 100644
--- a/MediaBrowser.Providers/Plugins/StudioImages/Configuration/config.html
+++ b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/config.html
@@ -9,8 +9,8 @@
<div class="content-primary">
<form class="configForm">
<div class="inputContainer">
- <input is="emby-input" type="text" id="repository" required label="Repository" />
- <div class="fieldDescription">This can be any Jellyfin-compatible artwork repository.</div>
+ <input is="emby-input" type="text" id="repository" label="Repository" />
+ <div class="fieldDescription">This can be any Jellyfin-compatible artwork repository. Leave blank to use default repository.</div>
</div>
<br />
<div>
@@ -44,7 +44,7 @@
Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
- config.RepositoryUrl = document.querySelector('#server').value;
+ config.RepositoryUrl = document.querySelector('#repository').value;
ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
});
diff --git a/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs b/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs
index f4941565f..5e653d039 100644
--- a/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs
+++ b/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs
@@ -7,6 +7,7 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization;
+using MediaBrowser.Providers.Plugins.StudioImages.Configuration;
namespace MediaBrowser.Providers.Plugins.StudioImages
{
diff --git a/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs b/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs
index ce267ac84..ef822a22a 100644
--- a/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs
@@ -18,29 +18,24 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.StudioImages;
-namespace MediaBrowser.Providers.Studios
+namespace MediaBrowser.Providers.Plugins.StudioImages
{
public class StudiosImageProvider : IRemoteImageProvider
{
private readonly IServerConfigurationManager _config;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IFileSystem _fileSystem;
- private readonly string repositoryUrl;
public StudiosImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory, IFileSystem fileSystem)
{
_config = config;
_httpClientFactory = httpClientFactory;
_fileSystem = fileSystem;
- repositoryUrl = Plugin.Instance.Configuration.RepositoryUrl;
}
public string Name => "Artwork Repository";
- public int Order => 0;
-
public bool Supports(BaseItem item)
{
return item is Studio;
@@ -98,12 +93,12 @@ namespace MediaBrowser.Providers.Studios
private string GetUrl(string image, string filename)
{
- return string.Format(CultureInfo.InvariantCulture, "{0}/images/{1}/{2}.jpg", repositoryUrl, image, filename);
+ return string.Format(CultureInfo.InvariantCulture, "{0}/images/{1}/{2}.jpg", GetRepositoryUrl(), image, filename);
}
private Task<string> EnsureThumbsList(string file, CancellationToken cancellationToken)
{
- string url = string.Format(CultureInfo.InvariantCulture, "{0}/thumbs.txt", repositoryUrl);
+ string url = string.Format(CultureInfo.InvariantCulture, "{0}/thumbs.txt", GetRepositoryUrl());
return EnsureList(url, file, _fileSystem, cancellationToken);
}
@@ -169,5 +164,8 @@ namespace MediaBrowser.Providers.Studios
}
}
}
+
+ private string GetRepositoryUrl()
+ => Plugin.Instance.Configuration.RepositoryUrl;
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
index 234d717bf..685eb222f 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
@@ -84,8 +84,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
public static bool IsTrailerType(Video video)
{
return video.Site.Equals("youtube", StringComparison.OrdinalIgnoreCase)
- && (!video.Type.Equals("trailer", StringComparison.OrdinalIgnoreCase)
- || !video.Type.Equals("teaser", StringComparison.OrdinalIgnoreCase));
+ && (video.Type.Equals("trailer", StringComparison.OrdinalIgnoreCase)
+ || video.Type.Equals("teaser", StringComparison.OrdinalIgnoreCase));
}
/// <summary>
diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
index f49492f33..c09b6d813 100644
--- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs
+++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
@@ -8,6 +8,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
@@ -40,6 +41,7 @@ namespace MediaBrowser.Providers.TV
{
await base.AfterMetadataRefresh(item, refreshOptions, cancellationToken).ConfigureAwait(false);
+ RemoveObsoleteEpisodes(item);
RemoveObsoleteSeasons(item);
await FillInMissingSeasonsAsync(item, cancellationToken).ConfigureAwait(false);
}
@@ -121,6 +123,61 @@ namespace MediaBrowser.Providers.TV
}
}
+ private void RemoveObsoleteEpisodes(Series series)
+ {
+ var episodes = series.GetEpisodes(null, new DtoOptions()).OfType<Episode>().ToList();
+ var numberOfEpisodes = episodes.Count;
+ // TODO: O(n^2), but can it be done faster without overcomplicating it?
+ for (var i = 0; i < numberOfEpisodes; i++)
+ {
+ var currentEpisode = episodes[i];
+ // The outer loop only examines virtual episodes
+ if (!currentEpisode.IsVirtualItem)
+ {
+ continue;
+ }
+
+ // Virtual episodes without an episode number are practically orphaned and should be deleted
+ if (!currentEpisode.IndexNumber.HasValue)
+ {
+ DeleteEpisode(currentEpisode);
+ continue;
+ }
+
+ for (var j = i + 1; j < numberOfEpisodes; j++)
+ {
+ var comparisonEpisode = episodes[j];
+ // The inner loop is only for "physical" episodes
+ if (comparisonEpisode.IsVirtualItem
+ || currentEpisode.ParentIndexNumber != comparisonEpisode.ParentIndexNumber
+ || !comparisonEpisode.ContainsEpisodeNumber(currentEpisode.IndexNumber.Value))
+ {
+ continue;
+ }
+
+ DeleteEpisode(currentEpisode);
+ break;
+ }
+ }
+ }
+
+ private void DeleteEpisode(Episode episode)
+ {
+ Logger.LogInformation(
+ "Removing virtual episode S{SeasonNumber}E{EpisodeNumber} in series {SeriesName}",
+ episode.ParentIndexNumber,
+ episode.IndexNumber,
+ episode.SeriesName);
+
+ LibraryManager.DeleteItem(
+ episode,
+ new DeleteOptions
+ {
+ DeleteFileLocation = true
+ },
+ false);
+ }
+
/// <summary>
/// Creates seasons for all episodes that aren't in a season folder.
/// If no season number can be determined, a dummy season will be created.
diff --git a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
index 86b05c475..ec062152b 100644
--- a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
+++ b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
@@ -27,7 +27,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
index 09ff84044..da348239a 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
@@ -1330,7 +1330,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
};
/// <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>
diff --git a/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs
index 71b58cddb..813d75f6c 100644
--- a/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs
@@ -58,7 +58,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
{
var formatString = ConfigurationManager.GetNfoConfiguration().ReleaseDateFormat;
- writer.WriteElementString("disbanded", artist.EndDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
+ writer.WriteElementString("disbanded", artist.EndDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
}
var albums = artist
diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
index 39bd87e96..740ca4c49 100644
--- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
@@ -473,7 +473,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
writer.WriteElementString("lockedfields", string.Join('|', item.LockedFields));
}
- writer.WriteElementString("dateadded", item.DateCreated.ToLocalTime().ToString(DateAddedFormat, CultureInfo.InvariantCulture));
+ writer.WriteElementString("dateadded", item.DateCreated.ToString(DateAddedFormat, CultureInfo.InvariantCulture));
writer.WriteElementString("title", item.Name ?? string.Empty);
@@ -601,16 +601,16 @@ namespace MediaBrowser.XbmcMetadata.Savers
{
writer.WriteElementString(
"formed",
- item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
+ item.PremiereDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
}
else
{
writer.WriteElementString(
"premiered",
- item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
+ item.PremiereDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
writer.WriteElementString(
"releasedate",
- item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
+ item.PremiereDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
}
}
@@ -622,7 +622,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
writer.WriteElementString(
"enddate",
- item.EndDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
+ item.EndDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
}
}
@@ -891,7 +891,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
{
writer.WriteElementString(
"lastplayed",
- userdata.LastPlayedDate.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture).ToLowerInvariant());
+ userdata.LastPlayedDate.Value.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture).ToLowerInvariant());
}
writer.WriteStartElement("resume");
diff --git a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs
index 2cd3fdf02..7ac465205 100644
--- a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs
@@ -75,7 +75,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
{
var formatString = ConfigurationManager.GetNfoConfiguration().ReleaseDateFormat;
- writer.WriteElementString("aired", episode.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
+ writer.WriteElementString("aired", episode.PremiereDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
}
if (!episode.ParentIndexNumber.HasValue || episode.ParentIndexNumber.Value == 0)
diff --git a/README.md b/README.md
index 7f6daca68..e81ea64c0 100644
--- a/README.md
+++ b/README.md
@@ -22,27 +22,24 @@
<a href="https://hub.docker.com/r/jellyfin/jellyfin">
<img alt="Docker Pull Count" src="https://img.shields.io/docker/pulls/jellyfin/jellyfin.svg"/>
</a>
-</br>
+<br/>
<a href="https://opencollective.com/jellyfin">
<img alt="Donate" src="https://img.shields.io/opencollective/all/jellyfin.svg?label=backers"/>
</a>
<a href="https://features.jellyfin.org">
<img alt="Submit Feature Requests" src="https://img.shields.io/badge/fider-vote%20on%20features-success.svg"/>
</a>
-<a href="https://matrix.to/#/+jellyfin:matrix.org">
+<a href="https://matrix.to/#/#jellyfinorg:matrix.org">
<img alt="Chat on Matrix" src="https://img.shields.io/matrix/jellyfin:matrix.org.svg?logo=matrix"/>
</a>
<a href="https://www.reddit.com/r/jellyfin">
<img alt="Join our Subreddit" src="https://img.shields.io/badge/reddit-r%2Fjellyfin-%23FF5700.svg"/>
</a>
<a href="https://github.com/jellyfin/jellyfin/releases.atom">
-<img alt="Release RSS Feed"" src="https://img.shields.io/badge/rss-releases-ffa500?logo=rss" />
+<img alt="Release RSS Feed" src="https://img.shields.io/badge/rss-releases-ffa500?logo=rss" />
</a>
<a href="https://github.com/jellyfin/jellyfin/commits/master.atom">
-<img alt="Master Commits RSS Feed"" src="https://img.shields.io/badge/rss-commits-ffa500?logo=rss" />
-</a>
-<a href="https://lgtm.com/projects/g/jellyfin/jellyfin/alerts/">
-<img alt="Total LGTM alerts" src="https://img.shields.io/lgtm/alerts/g/jellyfin/jellyfin.svg?logo=lgtm&logoWidth=18"/>
+<img alt="Master Commits RSS Feed" src="https://img.shields.io/badge/rss-commits-ffa500?logo=rss" />
</a>
</p>
diff --git a/SharedVersion.cs b/SharedVersion.cs
index 5e2f151a2..238ef83bd 100644
--- a/SharedVersion.cs
+++ b/SharedVersion.cs
@@ -1,4 +1,4 @@
using System.Reflection;
-[assembly: AssemblyVersion("10.8.0")]
-[assembly: AssemblyFileVersion("10.8.0")]
+[assembly: AssemblyVersion("10.9.0")]
+[assembly: AssemblyFileVersion("10.9.0")]
diff --git a/build.yaml b/build.yaml
index 18434ee00..3f676a5cf 100644
--- a/build.yaml
+++ b/build.yaml
@@ -1,7 +1,7 @@
---
# We just wrap `build` so this is really it
name: "jellyfin"
-version: "10.8.0"
+version: "10.9.0"
packages:
- debian.amd64
- debian.arm64
diff --git a/debian/changelog b/debian/changelog
index 430594cac..0d744c02a 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+jellyfin-server (10.9.0-1) unstable; urgency=medium
+
+ * New upstream version 10.9.0; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.9.0
+
+ -- Jellyfin Packaging Team <packaging@jellyfin.org> Wed, 13 Jul 2022 20:58:08 -0600
+
jellyfin-server (10.8.0-1) unstable; urgency=medium
* Forthcoming stable release
diff --git a/debian/conf/jellyfin-sudoers b/debian/conf/jellyfin-sudoers
index f84e7454f..795fd17e8 100644
--- a/debian/conf/jellyfin-sudoers
+++ b/debian/conf/jellyfin-sudoers
@@ -30,8 +30,4 @@ Defaults!RESTARTSERVER_INITD !requiretty
Defaults!STARTSERVER_INITD !requiretty
Defaults!STOPSERVER_INITD !requiretty
-#Allow the server to mount iso images
-jellyfin ALL=(ALL) NOPASSWD: /bin/mount
-jellyfin ALL=(ALL) NOPASSWD: /bin/umount
-
Defaults:jellyfin !requiretty
diff --git a/debian/conf/jellyfin.service.conf b/debian/conf/jellyfin.service.conf
index 1b69dd74e..1f92d7d94 100644
--- a/debian/conf/jellyfin.service.conf
+++ b/debian/conf/jellyfin.service.conf
@@ -3,5 +3,53 @@
# Use this file to override the user or environment file location.
[Service]
+# Alter the user that Jellyfin runs as
#User = jellyfin
+
+# Alter where environment variables are sourced from
#EnvironmentFile = /etc/default/jellyfin
+
+# Service hardening options
+# These were added in PR #6953 to solve issue #6952, but some combination of
+# them causes "restart.sh" functionality to break with the following error:
+# sudo: effective uid is not 0, is /usr/bin/sudo on a file system with the
+# 'nosuid' option set or an NFS file system without root privileges?
+# See issue #7503 for details on the troubleshooting that went into this.
+# Since these were added for NixOS specifically and are above and beyond
+# what 99% of systemd units do, they have been moved here as optional
+# additional flags to set for maximum system security and can be enabled at
+# the administrator's or package maintainer's discretion.
+# Uncomment these only if you know what you're doing, and doing so may cause
+# bugs with in-server Restart and potentially other functionality as well.
+#NoNewPrivileges=true
+#SystemCallArchitectures=native
+#RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
+#RestrictNamespaces=false
+#RestrictRealtime=true
+#RestrictSUIDSGID=true
+#ProtectControlGroups=false
+#ProtectHostname=true
+#ProtectKernelLogs=false
+#ProtectKernelModules=false
+#ProtectKernelTunables=false
+#LockPersonality=true
+#PrivateTmp=false
+#PrivateDevices=false
+#PrivateUsers=true
+#RemoveIPC=true
+#SystemCallFilter=~@clock
+#SystemCallFilter=~@aio
+#SystemCallFilter=~@chown
+#SystemCallFilter=~@cpu-emulation
+#SystemCallFilter=~@debug
+#SystemCallFilter=~@keyring
+#SystemCallFilter=~@memlock
+#SystemCallFilter=~@module
+#SystemCallFilter=~@mount
+#SystemCallFilter=~@obsolete
+#SystemCallFilter=~@privileged
+#SystemCallFilter=~@raw-io
+#SystemCallFilter=~@reboot
+#SystemCallFilter=~@setuid
+#SystemCallFilter=~@swap
+#SystemCallErrorNumber=EPERM
diff --git a/debian/control b/debian/control
index da9aa94d4..dea48d948 100644
--- a/debian/control
+++ b/debian/control
@@ -22,7 +22,7 @@ Depends: at,
libsqlite3-0,
libfontconfig1,
libfreetype6,
- libssl1.1
+ libssl1.1 | libssl3
Recommends: jellyfin-web, sudo
Description: Jellyfin is the Free Software Media System.
This package provides the Jellyfin server backend and API.
diff --git a/debian/jellyfin.service b/debian/jellyfin.service
index 064e10537..1150924a0 100644
--- a/debian/jellyfin.service
+++ b/debian/jellyfin.service
@@ -8,43 +8,10 @@ EnvironmentFile = /etc/default/jellyfin
User = jellyfin
Group = jellyfin
WorkingDirectory = /var/lib/jellyfin
-ExecStart = /usr/bin/jellyfin ${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} ${JELLYFIN_ADDITIONAL_OPTS}
+ExecStart = /usr/bin/jellyfin $JELLYFIN_WEB_OPT $JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_SERVICE_OPT $JELLYFIN_NOWEBAPP_OPT $JELLYFIN_ADDITIONAL_OPTS
Restart = on-failure
TimeoutSec = 15
SuccessExitStatus=0 143
-NoNewPrivileges=true
-SystemCallArchitectures=native
-RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
-RestrictNamespaces=false
-RestrictRealtime=true
-RestrictSUIDSGID=true
-ProtectControlGroups=false
-ProtectHostname=true
-ProtectKernelLogs=false
-ProtectKernelModules=false
-ProtectKernelTunables=false
-LockPersonality=true
-PrivateTmp=false
-PrivateDevices=false
-PrivateUsers=true
-RemoveIPC=true
-SystemCallFilter=~@clock
-SystemCallFilter=~@aio
-SystemCallFilter=~@chown
-SystemCallFilter=~@cpu-emulation
-SystemCallFilter=~@debug
-SystemCallFilter=~@keyring
-SystemCallFilter=~@memlock
-SystemCallFilter=~@module
-SystemCallFilter=~@mount
-SystemCallFilter=~@obsolete
-SystemCallFilter=~@privileged
-SystemCallFilter=~@raw-io
-SystemCallFilter=~@reboot
-SystemCallFilter=~@setuid
-SystemCallFilter=~@swap
-SystemCallErrorNumber=EPERM
-
[Install]
WantedBy = multi-user.target
diff --git a/debian/metapackage/jellyfin b/debian/metapackage/jellyfin
index a9a0ae5b0..8787c3a49 100644
--- a/debian/metapackage/jellyfin
+++ b/debian/metapackage/jellyfin
@@ -5,7 +5,7 @@ Homepage: https://jellyfin.org
Standards-Version: 3.9.2
Package: jellyfin
-Version: 10.8.0
+Version: 10.9.0
Maintainer: Jellyfin Packaging Team <packaging@jellyfin.org>
Depends: jellyfin-server, jellyfin-web
Description: Provides the Jellyfin Free Software Media System
diff --git a/debian/rules b/debian/rules
index 64e2b48ea..f55b1807e 100755
--- a/debian/rules
+++ b/debian/rules
@@ -40,7 +40,7 @@ override_dh_clistrip:
override_dh_auto_build:
dotnet publish -maxcpucount:1 --configuration $(CONFIG) --output='$(CURDIR)/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \
- "-p:DebugSymbols=false;DebugType=none" Jellyfin.Server
+ -p:DebugSymbols=false -p:DebugType=none Jellyfin.Server
override_dh_auto_clean:
dotnet clean -maxcpucount:1 --configuration $(CONFIG) Jellyfin.Server || true
diff --git a/deployment/Dockerfile.centos.amd64 b/deployment/Dockerfile.centos.amd64
index a4638c03b..0bae42bc8 100644
--- a/deployment/Dockerfile.centos.amd64
+++ b/deployment/Dockerfile.centos.amd64
@@ -13,7 +13,7 @@ RUN yum update -yq \
&& yum install -yq @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git wget
# Install DotNET SDK
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/c505a449-9ecf-4352-8629-56216f521616/bd6807340faae05b61de340c8bf161e8/dotnet-sdk-6.0.201-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/8159607a-e686-4ead-ac99-b4c97290a5fd/ec6070b1b2cc0651ebe57cf1bd411315/dotnet-sdk-6.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.debian.amd64 b/deployment/Dockerfile.debian.amd64
index daba0eb7d..c7bb5f768 100644
--- a/deployment/Dockerfile.debian.amd64
+++ b/deployment/Dockerfile.debian.amd64
@@ -12,7 +12,7 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
&& apt-get install -yqq --no-install-recommends \
- apt-transport-https debhelper gnupg devscripts build-essential mmv \
+ debhelper gnupg devscripts build-essential mmv \
libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev \
libssl1.1 liblttng-ust0
diff --git a/deployment/Dockerfile.debian.arm64 b/deployment/Dockerfile.debian.arm64
index db4e7f817..a0ca9b3f3 100644
--- a/deployment/Dockerfile.debian.arm64
+++ b/deployment/Dockerfile.debian.arm64
@@ -12,7 +12,7 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
&& apt-get install -yqq --no-install-recommends \
- apt-transport-https debhelper gnupg devscripts build-essential mmv
+ debhelper gnupg devscripts build-essential mmv
# Prepare the cross-toolchain
RUN dpkg --add-architecture arm64 \
diff --git a/deployment/Dockerfile.debian.armhf b/deployment/Dockerfile.debian.armhf
index 9b008e7fb..42a55ebfe 100644
--- a/deployment/Dockerfile.debian.armhf
+++ b/deployment/Dockerfile.debian.armhf
@@ -12,7 +12,7 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
&& apt-get install -yqq --no-install-recommends \
- apt-transport-https debhelper gnupg devscripts build-essential mmv
+ debhelper gnupg devscripts build-essential mmv
# Prepare the cross-toolchain
RUN dpkg --add-architecture armhf \
diff --git a/deployment/Dockerfile.docker.amd64 b/deployment/Dockerfile.docker.amd64
index b2bd40713..3fd3fa33c 100644
--- a/deployment/Dockerfile.docker.amd64
+++ b/deployment/Dockerfile.docker.amd64
@@ -10,4 +10,4 @@ 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:DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 -p:DebugSymbols=false -p:DebugType=none
diff --git a/deployment/Dockerfile.docker.arm64 b/deployment/Dockerfile.docker.arm64
index fc60f1624..e3cc92bcb 100644
--- a/deployment/Dockerfile.docker.arm64
+++ b/deployment/Dockerfile.docker.arm64
@@ -10,4 +10,4 @@ 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:DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 -p:DebugSymbols=false -p:DebugType=none
diff --git a/deployment/Dockerfile.docker.armhf b/deployment/Dockerfile.docker.armhf
index f5cc47d83..3a5df2e24 100644
--- a/deployment/Dockerfile.docker.armhf
+++ b/deployment/Dockerfile.docker.armhf
@@ -10,4 +10,4 @@ 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:DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm -p:DebugSymbols=false -p:DebugType=none
diff --git a/deployment/Dockerfile.fedora.amd64 b/deployment/Dockerfile.fedora.amd64
index 8f564bb12..20aa777b6 100644
--- a/deployment/Dockerfile.fedora.amd64
+++ b/deployment/Dockerfile.fedora.amd64
@@ -1,4 +1,4 @@
-FROM fedora:33
+FROM fedora:36
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
@@ -9,10 +9,10 @@ ENV IS_DOCKER=YES
# Prepare Fedora environment
RUN dnf update -yq \
- && dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd wget
+ && dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd wget make
# Install DotNET SDK
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/c505a449-9ecf-4352-8629-56216f521616/bd6807340faae05b61de340c8bf161e8/dotnet-sdk-6.0.201-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/8159607a-e686-4ead-ac99-b4c97290a5fd/ec6070b1b2cc0651ebe57cf1bd411315/dotnet-sdk-6.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.linux.amd64 b/deployment/Dockerfile.linux.amd64
index 2c7e41cac..14b580d11 100644
--- a/deployment/Dockerfile.linux.amd64
+++ b/deployment/Dockerfile.linux.amd64
@@ -12,7 +12,7 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
&& apt-get install -yqq --no-install-recommends \
- apt-transport-https debhelper gnupg devscripts unzip \
+ debhelper gnupg devscripts unzip \
mmv libcurl4-openssl-dev libfontconfig1-dev \
libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
diff --git a/deployment/Dockerfile.linux.amd64-musl b/deployment/Dockerfile.linux.amd64-musl
index e903cf1d3..672c3f269 100644
--- a/deployment/Dockerfile.linux.amd64-musl
+++ b/deployment/Dockerfile.linux.amd64-musl
@@ -12,7 +12,7 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
&& apt-get install -yqq --no-install-recommends \
- apt-transport-https debhelper gnupg devscripts unzip \
+ debhelper gnupg devscripts unzip \
mmv libcurl4-openssl-dev libfontconfig1-dev \
libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
diff --git a/deployment/Dockerfile.linux.arm64 b/deployment/Dockerfile.linux.arm64
index 0dd3c5e4e..f2a178be3 100644
--- a/deployment/Dockerfile.linux.arm64
+++ b/deployment/Dockerfile.linux.arm64
@@ -12,7 +12,7 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
&& apt-get install -yqq --no-install-recommends \
- apt-transport-https debhelper gnupg devscripts unzip \
+ debhelper gnupg devscripts unzip \
mmv libcurl4-openssl-dev libfontconfig1-dev \
libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
diff --git a/deployment/Dockerfile.linux.armhf b/deployment/Dockerfile.linux.armhf
index 16a8218e1..025716f45 100644
--- a/deployment/Dockerfile.linux.armhf
+++ b/deployment/Dockerfile.linux.armhf
@@ -12,7 +12,7 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
&& apt-get install -yqq --no-install-recommends \
- apt-transport-https debhelper gnupg devscripts unzip \
+ debhelper gnupg devscripts unzip \
mmv libcurl4-openssl-dev libfontconfig1-dev \
libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
diff --git a/deployment/Dockerfile.linux.musl-linux-arm64 b/deployment/Dockerfile.linux.musl-linux-arm64
new file mode 100644
index 000000000..2da72e4ae
--- /dev/null
+++ b/deployment/Dockerfile.linux.musl-linux-arm64
@@ -0,0 +1,26 @@
+FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim
+# Docker build arguments
+ARG SOURCE_DIR=/jellyfin
+ARG ARTIFACT_DIR=/dist
+# Docker run environment
+ENV SOURCE_DIR=/jellyfin
+ENV ARTIFACT_DIR=/dist
+ENV DEB_BUILD_OPTIONS=noddebs
+ENV ARCH=arm64
+ENV IS_DOCKER=YES
+
+# Prepare Debian build environment
+RUN apt-get update -yqq \
+ && apt-get install -yqq --no-install-recommends \
+ apt-transport-https debhelper gnupg devscripts unzip \
+ mmv libcurl4-openssl-dev libfontconfig1-dev \
+ libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
+
+# Link to docker-build script
+RUN ln -sf ${SOURCE_DIR}/deployment/build.linux.musl-linux-arm64 /build.sh
+
+VOLUME ${SOURCE_DIR}/
+
+VOLUME ${ARTIFACT_DIR}/
+
+ENTRYPOINT ["/build.sh"]
diff --git a/deployment/Dockerfile.macos b/deployment/Dockerfile.macos
index 699ab2d40..f63dc2906 100644
--- a/deployment/Dockerfile.macos
+++ b/deployment/Dockerfile.macos
@@ -12,7 +12,7 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
&& apt-get install -yqq --no-install-recommends \
- apt-transport-https debhelper gnupg devscripts \
+ debhelper gnupg devscripts \
mmv libcurl4-openssl-dev libfontconfig1-dev \
libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
diff --git a/deployment/Dockerfile.portable b/deployment/Dockerfile.portable
index b567d7bce..e48e2d41a 100644
--- a/deployment/Dockerfile.portable
+++ b/deployment/Dockerfile.portable
@@ -11,7 +11,7 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
&& apt-get install -yqq --no-install-recommends \
- apt-transport-https debhelper gnupg devscripts \
+ debhelper gnupg devscripts \
mmv libcurl4-openssl-dev libfontconfig1-dev \
libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64
index f5a5b54fc..ccc0f76cd 100644
--- a/deployment/Dockerfile.ubuntu.amd64
+++ b/deployment/Dockerfile.ubuntu.amd64
@@ -12,12 +12,12 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
&& apt-get install -yqq --no-install-recommends \
- apt-transport-https debhelper gnupg wget ca-certificates devscripts \
+ debhelper gnupg wget ca-certificates devscripts \
mmv build-essential libcurl4-openssl-dev libfontconfig1-dev \
libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
# Install dotnet repository
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/c505a449-9ecf-4352-8629-56216f521616/bd6807340faae05b61de340c8bf161e8/dotnet-sdk-6.0.201-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/8159607a-e686-4ead-ac99-b4c97290a5fd/ec6070b1b2cc0651ebe57cf1bd411315/dotnet-sdk-6.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64
index 7ca0f6470..893180974 100644
--- a/deployment/Dockerfile.ubuntu.arm64
+++ b/deployment/Dockerfile.ubuntu.arm64
@@ -12,11 +12,11 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
&& apt-get install -yqq --no-install-recommends \
- apt-transport-https debhelper gnupg wget ca-certificates devscripts \
+ debhelper gnupg wget ca-certificates devscripts \
mmv build-essential lsb-release
# Install dotnet repository
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/c505a449-9ecf-4352-8629-56216f521616/bd6807340faae05b61de340c8bf161e8/dotnet-sdk-6.0.201-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/8159607a-e686-4ead-ac99-b4c97290a5fd/ec6070b1b2cc0651ebe57cf1bd411315/dotnet-sdk-6.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf
index 384e49bf0..bf1edf777 100644
--- a/deployment/Dockerfile.ubuntu.armhf
+++ b/deployment/Dockerfile.ubuntu.armhf
@@ -12,11 +12,11 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
&& apt-get install -yqq --no-install-recommends \
- apt-transport-https debhelper gnupg wget ca-certificates devscripts \
+ debhelper gnupg wget ca-certificates devscripts \
mmv build-essential lsb-release
# Install dotnet repository
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/c505a449-9ecf-4352-8629-56216f521616/bd6807340faae05b61de340c8bf161e8/dotnet-sdk-6.0.201-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/8159607a-e686-4ead-ac99-b4c97290a5fd/ec6070b1b2cc0651ebe57cf1bd411315/dotnet-sdk-6.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.windows.amd64 b/deployment/Dockerfile.windows.amd64
index b9543a7c9..655300d47 100644
--- a/deployment/Dockerfile.windows.amd64
+++ b/deployment/Dockerfile.windows.amd64
@@ -11,7 +11,7 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
&& apt-get install -yqq --no-install-recommends \
- apt-transport-https debhelper gnupg devscripts unzip \
+ debhelper gnupg devscripts unzip \
mmv libcurl4-openssl-dev libfontconfig1-dev \
libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 zip
diff --git a/deployment/build.linux.amd64 b/deployment/build.linux.amd64
index a1e7f661a..05059e4ed 100755
--- a/deployment/build.linux.amd64
+++ b/deployment/build.linux.amd64
@@ -16,7 +16,7 @@ else
fi
# Build archives
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
tar -czf jellyfin-server_${version}_linux-amd64.tar.gz -C dist jellyfin-server_${version}
rm -rf dist/jellyfin-server_${version}
diff --git a/deployment/build.linux.amd64-musl b/deployment/build.linux.amd64-musl
index 72444c05e..0ee4b05fb 100755
--- a/deployment/build.linux.amd64-musl
+++ b/deployment/build.linux.amd64-musl
@@ -16,7 +16,7 @@ else
fi
# Build archives
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-musl-x64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-musl-x64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
tar -czf jellyfin-server_${version}_linux-amd64-musl.tar.gz -C dist jellyfin-server_${version}
rm -rf dist/jellyfin-server_${version}
diff --git a/deployment/build.linux.arm64 b/deployment/build.linux.arm64
index e362607a7..6e36db0eb 100755
--- a/deployment/build.linux.arm64
+++ b/deployment/build.linux.arm64
@@ -16,7 +16,7 @@ else
fi
# Build archives
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-arm64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-arm64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
tar -czf jellyfin-server_${version}_linux-arm64.tar.gz -C dist jellyfin-server_${version}
rm -rf dist/jellyfin-server_${version}
diff --git a/deployment/build.linux.armhf b/deployment/build.linux.armhf
index c0d0607fc..f83eeebf1 100755
--- a/deployment/build.linux.armhf
+++ b/deployment/build.linux.armhf
@@ -16,7 +16,7 @@ else
fi
# Build archives
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-arm --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-arm --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
tar -czf jellyfin-server_${version}_linux-armhf.tar.gz -C dist jellyfin-server_${version}
rm -rf dist/jellyfin-server_${version}
diff --git a/deployment/build.linux.musl-linux-arm64 b/deployment/build.linux.musl-linux-arm64
new file mode 100755
index 000000000..38826ae7f
--- /dev/null
+++ b/deployment/build.linux.musl-linux-arm64
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+#= Generic Linux musl-linux-arm64 .tar.gz
+
+set -o errexit
+set -o xtrace
+
+# Move to source directory
+pushd ${SOURCE_DIR}
+
+# Get version
+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-musl-arm64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
+tar -czf jellyfin-server_${version}_linux-arm64-musl.tar.gz -C dist jellyfin-server_${version}
+rm -rf dist/jellyfin-server_${version}
+
+# Move the artifacts out
+mkdir -p ${ARTIFACT_DIR}/
+mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/
+
+if [[ ${IS_DOCKER} == YES ]]; then
+ chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+fi
+
+popd
diff --git a/deployment/build.macos b/deployment/build.macos
index 6255c80cb..01c640c8b 100755
--- a/deployment/build.macos
+++ b/deployment/build.macos
@@ -16,7 +16,7 @@ else
fi
# Build archives
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
tar -czf jellyfin-server_${version}_macos-amd64.tar.gz -C dist jellyfin-server_${version}
rm -rf dist/jellyfin-server_${version}
diff --git a/deployment/build.portable b/deployment/build.portable
index a6c741881..27e5e987f 100755
--- a/deployment/build.portable
+++ b/deployment/build.portable
@@ -16,7 +16,7 @@ else
fi
# Build archives
-dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=false"
+dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=false
tar -czf jellyfin-server_${version}_portable.tar.gz -C dist jellyfin-server_${version}
rm -rf dist/jellyfin-server_${version}
diff --git a/deployment/build.windows.amd64 b/deployment/build.windows.amd64
index a9daa6a23..30b252beb 100755
--- a/deployment/build.windows.amd64
+++ b/deployment/build.windows.amd64
@@ -23,7 +23,7 @@ fi
output_dir="dist/jellyfin-server_${version}"
# Build binary
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output ${output_dir}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output ${output_dir}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
# Prepare addins
addin_build_dir="$( mktemp -d )"
diff --git a/fedora/Makefile b/fedora/Makefile
index 261fd262d..3188cf603 100644
--- a/fedora/Makefile
+++ b/fedora/Makefile
@@ -7,14 +7,13 @@ SRPM := jellyfin-$(subst -,~,$(VERSION))-$(RELEASE)$(shell rpm --eval %dist).
TARBALL :=$(NAME)-$(subst -,~,$(VERSION)).tar.gz
epel-7-x86_64_repos := https://packages.microsoft.com/rhel/7/prod/
-epel-8-x86_64_repos := https://download.copr.fedorainfracloud.org/results/@dotnet-sig/dotnet-preview/$(TARGET)/
-fedora_repos := https://download.copr.fedorainfracloud.org/results/@dotnet-sig/dotnet-preview/$(TARGET)/
-fedora-34-x86_64_repos := $(fedora_repos)
-fedora-35-x86_64_repos := $(fedora_repos)
-fedora-34-x86_64_repos := $(fedora_repos)
+
+fed_ver := $(shell rpm -E %fedora)
+# fallback when not running on Fedora
+fed_ver ?= 36
+TARGET ?= fedora-$(fed_ver)-x86_64
outdir ?= $(PWD)/$(DIR)/
-TARGET ?= fedora-35-x86_64
srpm: $(DIR)/$(SRPM)
tarball: $(DIR)/$(TARBALL)
diff --git a/fedora/README.md b/fedora/README.md
index 7ed6f7efc..d449b51c1 100644
--- a/fedora/README.md
+++ b/fedora/README.md
@@ -18,14 +18,6 @@ $ sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-re
$ sudo yum localinstall --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-7.noarch.rpm
```
-## ISO mounting
-
-To allow Jellyfin to mount/umount ISO files uncomment these two lines in `/etc/sudoers.d/jellyfin-sudoers`
-```
-# %jellyfin ALL=(ALL) NOPASSWD: /bin/mount
-# %jellyfin ALL=(ALL) NOPASSWD: /bin/umount
-```
-
## Building with dotnet
Jellyfin is build with `--self-contained` so no dotnet required for runtime.
@@ -40,4 +32,4 @@ $ sudo rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-
## TODO
-- [ ] OpenSUSE \ No newline at end of file
+- [ ] OpenSUSE
diff --git a/fedora/jellyfin.env b/fedora/jellyfin.env
index 56b7a3558..1ccd8196f 100644
--- a/fedora/jellyfin.env
+++ b/fedora/jellyfin.env
@@ -21,7 +21,7 @@ JELLYFIN_LOG_DIR="/var/log/jellyfin"
JELLYFIN_CACHE_DIR="/var/cache/jellyfin"
# web client path, installed by the jellyfin-web package
-JELLYFIN_WEB_OPT="--webdir=/usr/share/jellyfin-web"
+# JELLYFIN_WEB_OPT="--webdir=/usr/share/jellyfin-web"
# In-App service control
JELLYFIN_RESTART_OPT="--restartpath=/usr/libexec/jellyfin/restart.sh"
@@ -33,7 +33,7 @@ JELLYFIN_RESTART_OPT="--restartpath=/usr/libexec/jellyfin/restart.sh"
#JELLYFIN_SERVICE_OPT="--service"
# [OPTIONAL] run Jellyfin without the web app
-#JELLYFIN_NOWEBAPP_OPT="--noautorunwebapp"
+#JELLYFIN_NOWEBAPP_OPT="--nowebclient"
# [OPTIONAL] run Jellyfin with ASP.NET Server Garbage Collection (uses more RAM and less CPU than Workstation GC)
# 0 = Workstation
diff --git a/fedora/jellyfin.override.conf b/fedora/jellyfin.override.conf
index 8652450bb..48b4de1e9 100644
--- a/fedora/jellyfin.override.conf
+++ b/fedora/jellyfin.override.conf
@@ -5,3 +5,49 @@
[Service]
#User = jellyfin
#EnvironmentFile = /etc/sysconfig/jellyfin
+
+# Service hardening options
+# These were added in PR #6953 to solve issue #6952, but some combination of
+# them causes "restart.sh" functionality to break with the following error:
+# sudo: effective uid is not 0, is /usr/bin/sudo on a file system with the
+# 'nosuid' option set or an NFS file system without root privileges?
+# See issue #7503 for details on the troubleshooting that went into this.
+# Since these were added for NixOS specifically and are above and beyond
+# what 99% of systemd units do, they have been moved here as optional
+# additional flags to set for maximum system security and can be enabled at
+# the administrator's or package maintainer's discretion.
+# Uncomment these only if you know what you're doing, and doing so may cause
+# bugs with in-server Restart and potentially other functionality as well.
+#NoNewPrivileges=true
+#SystemCallArchitectures=native
+#RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
+#RestrictNamespaces=false
+#RestrictRealtime=true
+#RestrictSUIDSGID=true
+#ProtectClock=true
+#ProtectControlGroups=false
+#ProtectHostname=true
+#ProtectKernelLogs=false
+#ProtectKernelModules=false
+#ProtectKernelTunables=false
+#LockPersonality=true
+#PrivateTmp=false
+#PrivateDevices=false
+#PrivateUsers=true
+#RemoveIPC=true
+#SystemCallFilter=~@clock
+#SystemCallFilter=~@aio
+#SystemCallFilter=~@chown
+#SystemCallFilter=~@cpu-emulation
+#SystemCallFilter=~@debug
+#SystemCallFilter=~@keyring
+#SystemCallFilter=~@memlock
+#SystemCallFilter=~@module
+#SystemCallFilter=~@mount
+#SystemCallFilter=~@obsolete
+#SystemCallFilter=~@privileged
+#SystemCallFilter=~@raw-io
+#SystemCallFilter=~@reboot
+#SystemCallFilter=~@setuid
+#SystemCallFilter=~@swap
+#SystemCallErrorNumber=EPERM
diff --git a/fedora/jellyfin.service b/fedora/jellyfin.service
index 1193ddb5b..2fb9f30e0 100644
--- a/fedora/jellyfin.service
+++ b/fedora/jellyfin.service
@@ -8,44 +8,10 @@ EnvironmentFile = /etc/sysconfig/jellyfin
User = jellyfin
Group = jellyfin
WorkingDirectory = /var/lib/jellyfin
-ExecStart = /usr/bin/jellyfin ${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} ${JELLYFIN_ADDITIONAL_OPTS}
+ExecStart = /usr/bin/jellyfin $JELLYFIN_WEB_OPT $JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_SERVICE_OPT $JELLYFIN_NOWEBAPP_OPT $JELLYFIN_ADDITIONAL_OPTS
Restart = on-failure
TimeoutSec = 15
SuccessExitStatus=0 143
-NoNewPrivileges=true
-SystemCallArchitectures=native
-RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
-RestrictNamespaces=false
-RestrictRealtime=true
-RestrictSUIDSGID=true
-ProtectClock=true
-ProtectControlGroups=false
-ProtectHostname=true
-ProtectKernelLogs=false
-ProtectKernelModules=false
-ProtectKernelTunables=false
-LockPersonality=true
-PrivateTmp=false
-PrivateDevices=false
-PrivateUsers=true
-RemoveIPC=true
-SystemCallFilter=~@clock
-SystemCallFilter=~@aio
-SystemCallFilter=~@chown
-SystemCallFilter=~@cpu-emulation
-SystemCallFilter=~@debug
-SystemCallFilter=~@keyring
-SystemCallFilter=~@memlock
-SystemCallFilter=~@module
-SystemCallFilter=~@mount
-SystemCallFilter=~@obsolete
-SystemCallFilter=~@privileged
-SystemCallFilter=~@raw-io
-SystemCallFilter=~@reboot
-SystemCallFilter=~@setuid
-SystemCallFilter=~@swap
-SystemCallErrorNumber=EPERM
-
[Install]
WantedBy = multi-user.target
diff --git a/fedora/jellyfin.spec b/fedora/jellyfin.spec
index e93944a20..a6771e389 100644
--- a/fedora/jellyfin.spec
+++ b/fedora/jellyfin.spec
@@ -1,16 +1,16 @@
%global debug_package %{nil}
# Set the dotnet runtime
%if 0%{?fedora}
-%global dotnet_runtime fedora-x64
+%global dotnet_runtime fedora.%{fedora}-x64
%else
%global dotnet_runtime centos-x64
%endif
Name: jellyfin
-Version: 10.8.0
+Version: 10.9.0
Release: 1%{?dist}
Summary: The Free Software Media System
-License: GPLv3
+License: GPLv2
URL: https://jellyfin.org
# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%%{version}.tar.gz`
Source0: jellyfin-server-%{version}.tar.gz
@@ -25,13 +25,16 @@ Source17: jellyfin-server-lowports.conf
%{?systemd_requires}
BuildRequires: systemd
BuildRequires: libcurl-devel, fontconfig-devel, freetype-devel, openssl-devel, glibc-devel, libicu-devel
-# Requirements not packaged in main repos
-# COPR @dotnet-sig/dotnet or
+# Requirements not packaged in RHEL 7 main repos, added via Makefile
# https://packages.microsoft.com/rhel/7/prod/
BuildRequires: dotnet-runtime-6.0, dotnet-sdk-6.0
Requires: %{name}-server = %{version}-%{release}, %{name}-web = %{version}-%{release}
-# Disable Automatic Dependency Processing
-AutoReqProv: no
+
+# Temporary (hopefully?) fix for https://github.com/jellyfin/jellyfin/issues/7471
+%if 0%{?fedora} >= 36
+%global __requires_exclude ^liblttng-ust\\.so\\.0.*$
+%endif
+
%description
Jellyfin is a free software media system that puts you in control of managing and streaming your media.
@@ -59,54 +62,74 @@ the Jellyfin server to bind to ports 80 and/or 443 for example.
%prep
%autosetup -n jellyfin-server-%{version} -b 0
-%build
-%install
+%build
export DOTNET_CLI_TELEMETRY_OPTOUT=1
-export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
export PATH=$PATH:/usr/local/bin
-dotnet publish --configuration Release --output='%{buildroot}%{_libdir}/jellyfin' --self-contained --runtime %{dotnet_runtime} \
- "-p:DebugSymbols=false;DebugType=none" Jellyfin.Server
-%{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE
-%{__install} -D -m 0644 %{SOURCE15} %{buildroot}%{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf
-%{__install} -D -m 0644 %{SOURCE17} %{buildroot}%{_unitdir}/jellyfin.service.d/jellyfin-server-lowports.conf
-%{__install} -D -m 0644 Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/jellyfin/logging.json
-%{__mkdir} -p %{buildroot}%{_bindir}
-tee %{buildroot}%{_bindir}/jellyfin << EOF
-#!/bin/sh
-exec %{_libdir}/jellyfin/jellyfin \${@}
-EOF
+# cannot use --output due to https://github.com/dotnet/sdk/issues/22220
+dotnet publish --configuration Release --self-contained --runtime %{dotnet_runtime} \
+ -p:DebugSymbols=false -p:DebugType=none Jellyfin.Server
+
+
+%install
+# Jellyfin files
+%{__mkdir} -p %{buildroot}%{_libdir}/jellyfin %{buildroot}%{_bindir}
+%{__cp} -r Jellyfin.Server/bin/Release/net6.0/%{dotnet_runtime}/publish/* %{buildroot}%{_libdir}/jellyfin
+ln -srf %{_libdir}/jellyfin/jellyfin %{buildroot}%{_bindir}/jellyfin
+%{__install} -D %{SOURCE14} %{buildroot}%{_libexecdir}/jellyfin/restart.sh
+
+# Jellyfin config
+%{__install} -D Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/jellyfin/logging.json
+%{__install} -D %{SOURCE12} %{buildroot}%{_sysconfdir}/sysconfig/jellyfin
+
+# system config
+%{__install} -D %{SOURCE16} %{buildroot}%{_prefix}/lib/firewalld/services/jellyfin.xml
+%{__install} -D %{SOURCE13} %{buildroot}%{_sysconfdir}/sudoers.d/jellyfin-sudoers
+%{__install} -D %{SOURCE15} %{buildroot}%{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf
+%{__install} -D %{SOURCE11} %{buildroot}%{_unitdir}/jellyfin.service
+
+# empty directories
%{__mkdir} -p %{buildroot}%{_sharedstatedir}/jellyfin
%{__mkdir} -p %{buildroot}%{_sysconfdir}/jellyfin
-%{__mkdir} -p %{buildroot}%{_var}/log/jellyfin
%{__mkdir} -p %{buildroot}%{_var}/cache/jellyfin
+%{__mkdir} -p %{buildroot}%{_var}/log/jellyfin
+
+# jellyfin-server-lowports subpackage
+%{__install} -D -m 0644 %{SOURCE17} %{buildroot}%{_unitdir}/jellyfin.service.d/jellyfin-server-lowports.conf
-%{__install} -D -m 0644 %{SOURCE11} %{buildroot}%{_unitdir}/jellyfin.service
-%{__install} -D -m 0644 %{SOURCE12} %{buildroot}%{_sysconfdir}/sysconfig/jellyfin
-%{__install} -D -m 0600 %{SOURCE13} %{buildroot}%{_sysconfdir}/sudoers.d/jellyfin-sudoers
-%{__install} -D -m 0755 %{SOURCE14} %{buildroot}%{_libexecdir}/jellyfin/restart.sh
-%{__install} -D -m 0644 %{SOURCE16} %{buildroot}%{_prefix}/lib/firewalld/services/jellyfin.xml
%files
# empty as this is just a meta-package
%files server
-%attr(755,root,root) %{_bindir}/jellyfin
-%{_libdir}/jellyfin/*
+%defattr(644,root,root,755)
+
+# Jellyfin files
+%{_bindir}/jellyfin
# Needs 755 else only root can run it since binary build by dotnet is 722
+%attr(755,root,root) %{_libdir}/jellyfin/createdump
%attr(755,root,root) %{_libdir}/jellyfin/jellyfin
-%{_unitdir}/jellyfin.service
-%{_libexecdir}/jellyfin/restart.sh
-%{_prefix}/lib/firewalld/services/jellyfin.xml
-%attr(755,jellyfin,jellyfin) %dir %{_sysconfdir}/jellyfin
+%{_libdir}/jellyfin/*
+%attr(755,root,root) %{_libexecdir}/jellyfin/restart.sh
+
+# Jellyfin config
+%config(noreplace) %attr(644,jellyfin,jellyfin) %{_sysconfdir}/jellyfin/logging.json
%config %{_sysconfdir}/sysconfig/jellyfin
+
+# system config
+%{_prefix}/lib/firewalld/services/jellyfin.xml
+%{_unitdir}/jellyfin.service
%config(noreplace) %attr(600,root,root) %{_sysconfdir}/sudoers.d/jellyfin-sudoers
%config(noreplace) %{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf
-%config(noreplace) %attr(644,jellyfin,jellyfin) %{_sysconfdir}/jellyfin/logging.json
+
+# empty directories
%attr(750,jellyfin,jellyfin) %dir %{_sharedstatedir}/jellyfin
-%attr(-,jellyfin,jellyfin) %dir %{_var}/log/jellyfin
+%attr(755,jellyfin,jellyfin) %dir %{_sysconfdir}/jellyfin
%attr(750,jellyfin,jellyfin) %dir %{_var}/cache/jellyfin
-%{_datadir}/licenses/jellyfin/LICENSE
+%attr(-, jellyfin,jellyfin) %dir %{_var}/log/jellyfin
+
+%license LICENSE
+
%files server-lowports
%{_unitdir}/jellyfin.service.d/jellyfin-server-lowports.conf
@@ -153,6 +176,8 @@ fi
%systemd_postun_with_restart jellyfin.service
%changelog
+* Wed Jul 13 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
+- New upstream version 10.9.0; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.9.0
* Mon Nov 29 2021 Brian J. Murrell <brian@interlinx.bc.ca>
- Add jellyfin-server-lowports.service drop-in in a server-lowports
subpackage to allow binding to low ports
diff --git a/fedora/jellyfin.sudoers b/fedora/jellyfin.sudoers
index 57a9e7b67..01c7f4e11 100644
--- a/fedora/jellyfin.sudoers
+++ b/fedora/jellyfin.sudoers
@@ -11,8 +11,4 @@ Defaults!RESTARTSERVER_SYSTEMD !requiretty
Defaults!STARTSERVER_SYSTEMD !requiretty
Defaults!STOPSERVER_SYSTEMD !requiretty
-# Allow the server to mount iso images
-jellyfin ALL=(ALL) NOPASSWD: /bin/mount
-jellyfin ALL=(ALL) NOPASSWD: /bin/umount
-
Defaults:jellyfin !requiretty
diff --git a/jellyfin.ruleset b/jellyfin.ruleset
index 1c834de82..5ac5f4923 100644
--- a/jellyfin.ruleset
+++ b/jellyfin.ruleset
@@ -84,6 +84,8 @@
<!-- error on CA2016: Forward the CancellationToken parameter to methods that take one
or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token -->
<Rule Id="CA2016" Action="Error" />
+ <!-- error on CA2201: Exception type System.Exception is not sufficiently specific -->
+ <Rule Id="CA2201" Action="Error" />
<!-- error on CA2215: Dispose methods should call base class dispose -->
<Rule Id="CA2215" Action="Error" />
<!-- error on CA2254: Template should be a static expression -->
diff --git a/src/Jellyfin.Extensions/EnumerableExtensions.cs b/src/Jellyfin.Extensions/EnumerableExtensions.cs
index b5fe93357..a31a57dc6 100644
--- a/src/Jellyfin.Extensions/EnumerableExtensions.cs
+++ b/src/Jellyfin.Extensions/EnumerableExtensions.cs
@@ -18,10 +18,7 @@ namespace Jellyfin.Extensions
/// <exception cref="ArgumentNullException">The source is null.</exception>
public static bool Contains(this IEnumerable<string> source, ReadOnlySpan<char> value, StringComparison stringComparison)
{
- if (source == null)
- {
- throw new ArgumentNullException(nameof(source));
- }
+ ArgumentNullException.ThrowIfNull(source);
if (source is IList<string> list)
{
diff --git a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
index 460c43829..f99cb0406 100644
--- a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
+++ b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
@@ -13,7 +13,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Extensions</PackageId>
- <VersionPrefix>10.8.0</VersionPrefix>
+ <VersionPrefix>10.9.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
@@ -34,7 +34,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/src/Jellyfin.Extensions/ReadOnlyListExtension.cs b/src/Jellyfin.Extensions/ReadOnlyListExtension.cs
index 7785cfb49..ba99bb534 100644
--- a/src/Jellyfin.Extensions/ReadOnlyListExtension.cs
+++ b/src/Jellyfin.Extensions/ReadOnlyListExtension.cs
@@ -57,5 +57,21 @@ namespace Jellyfin.Extensions
return -1;
}
+
+ /// <summary>
+ /// Get the first or default item from a list.
+ /// </summary>
+ /// <param name="source">The source list.</param>
+ /// <typeparam name="T">The type of item.</typeparam>
+ /// <returns>The first item or default if list is empty.</returns>
+ public static T? FirstOrDefault<T>(this IReadOnlyList<T>? source)
+ {
+ if (source is null || source.Count == 0)
+ {
+ return default;
+ }
+
+ return source[0];
+ }
}
}
diff --git a/src/Jellyfin.Extensions/SplitStringExtensions.cs b/src/Jellyfin.Extensions/SplitStringExtensions.cs
index 1d1c377f5..a4dc9fc6b 100644
--- a/src/Jellyfin.Extensions/SplitStringExtensions.cs
+++ b/src/Jellyfin.Extensions/SplitStringExtensions.cs
@@ -55,7 +55,7 @@ namespace Jellyfin.Extensions
public static Enumerator Split(this ReadOnlySpan<char> str, char separator) => new(str, separator);
/// <summary>
- /// Provides an enumerator for the substrings seperated by the separator.
+ /// Provides an enumerator for the substrings separated by the separator.
/// </summary>
[StructLayout(LayoutKind.Auto)]
public ref struct Enumerator
diff --git a/src/Jellyfin.Extensions/StringExtensions.cs b/src/Jellyfin.Extensions/StringExtensions.cs
index 3a7707253..b19be071b 100644
--- a/src/Jellyfin.Extensions/StringExtensions.cs
+++ b/src/Jellyfin.Extensions/StringExtensions.cs
@@ -1,4 +1,7 @@
using System;
+using System.Globalization;
+using System.Text;
+using System.Text.RegularExpressions;
namespace Jellyfin.Extensions
{
@@ -7,6 +10,44 @@ namespace Jellyfin.Extensions
/// </summary>
public static class StringExtensions
{
+ // Matches non-conforming unicode chars
+ // https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/
+ private static readonly Regex _nonConformingUnicode = new Regex("([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])|(\ufffd)");
+
+ /// <summary>
+ /// Removes the diacritics character from the strings.
+ /// </summary>
+ /// <param name="text">The string to act on.</param>
+ /// <returns>The string without diacritics character.</returns>
+ public static string RemoveDiacritics(this string text)
+ {
+ string withDiactritics = _nonConformingUnicode
+ .Replace(text, string.Empty)
+ .Normalize(NormalizationForm.FormD);
+
+ var withoutDiactritics = new StringBuilder();
+ foreach (char c in withDiactritics)
+ {
+ UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(c);
+ if (uc != UnicodeCategory.NonSpacingMark)
+ {
+ withoutDiactritics.Append(c);
+ }
+ }
+
+ return withoutDiactritics.ToString().Normalize(NormalizationForm.FormC);
+ }
+
+ /// <summary>
+ /// Checks whether or not the specified string has diacritics in it.
+ /// </summary>
+ /// <param name="text">The string to check.</param>
+ /// <returns>True if the string has diacritics, false otherwise.</returns>
+ public static bool HasDiacritics(this string text)
+ {
+ return !string.Equals(text, text.RemoveDiacritics(), StringComparison.Ordinal);
+ }
+
/// <summary>
/// Counts the number of occurrences of [needle] in the string.
/// </summary>
diff --git a/src/Jellyfin.MediaEncoding.Hls/Extractors/IKeyframeExtractor.cs b/src/Jellyfin.MediaEncoding.Hls/Extractors/IKeyframeExtractor.cs
index 497210f41..083e93de1 100644
--- a/src/Jellyfin.MediaEncoding.Hls/Extractors/IKeyframeExtractor.cs
+++ b/src/Jellyfin.MediaEncoding.Hls/Extractors/IKeyframeExtractor.cs
@@ -1,4 +1,3 @@
-using System;
using System.Diagnostics.CodeAnalysis;
using Jellyfin.MediaEncoding.Keyframes;
diff --git a/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj b/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj
index 30900039d..2ff7f9645 100644
--- a/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj
+++ b/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj
@@ -12,7 +12,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/src/Jellyfin.MediaEncoding.Hls/Playlist/CreateMainPlaylistRequest.cs b/src/Jellyfin.MediaEncoding.Hls/Playlist/CreateMainPlaylistRequest.cs
index ac28ca26a..8572a5eae 100644
--- a/src/Jellyfin.MediaEncoding.Hls/Playlist/CreateMainPlaylistRequest.cs
+++ b/src/Jellyfin.MediaEncoding.Hls/Playlist/CreateMainPlaylistRequest.cs
@@ -14,7 +14,8 @@ public class CreateMainPlaylistRequest
/// <param name="segmentContainer">The desired segment container eg. "ts".</param>
/// <param name="endpointPrefix">The URI prefix for the relative URL in the playlist.</param>
/// <param name="queryString">The desired query string to append (must start with ?).</param>
- public CreateMainPlaylistRequest(string filePath, int desiredSegmentLengthMs, long totalRuntimeTicks, string segmentContainer, string endpointPrefix, string queryString)
+ /// <param name="isRemuxingVideo">Whether the video is being remuxed.</param>
+ public CreateMainPlaylistRequest(string filePath, int desiredSegmentLengthMs, long totalRuntimeTicks, string segmentContainer, string endpointPrefix, string queryString, bool isRemuxingVideo)
{
FilePath = filePath;
DesiredSegmentLengthMs = desiredSegmentLengthMs;
@@ -22,6 +23,7 @@ public class CreateMainPlaylistRequest
SegmentContainer = segmentContainer;
EndpointPrefix = endpointPrefix;
QueryString = queryString;
+ IsRemuxingVideo = isRemuxingVideo;
}
/// <summary>
@@ -53,4 +55,9 @@ public class CreateMainPlaylistRequest
/// Gets the query string.
/// </summary>
public string QueryString { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether the video is being remuxed.
+ /// </summary>
+ public bool IsRemuxingVideo { get; }
}
diff --git a/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs b/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs
index 3382ba251..07a6d6050 100644
--- a/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs
+++ b/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs
@@ -34,7 +34,8 @@ public class DynamicHlsPlaylistGenerator : IDynamicHlsPlaylistGenerator
public string CreateMainPlaylist(CreateMainPlaylistRequest request)
{
IReadOnlyList<double> segments;
- if (TryExtractKeyframes(request.FilePath, out var keyframeData))
+ // For video transcodes it is sufficient with equal length segments as ffmpeg will create new keyframes
+ if (request.IsRemuxingVideo && TryExtractKeyframes(request.FilePath, out var keyframeData))
{
segments = ComputeSegments(keyframeData, request.DesiredSegmentLengthMs);
}
diff --git a/src/Jellyfin.MediaEncoding.Keyframes/FfProbe/FfProbeKeyframeExtractor.cs b/src/Jellyfin.MediaEncoding.Keyframes/FfProbe/FfProbeKeyframeExtractor.cs
index 320604e10..79aa8a354 100644
--- a/src/Jellyfin.MediaEncoding.Keyframes/FfProbe/FfProbeKeyframeExtractor.cs
+++ b/src/Jellyfin.MediaEncoding.Keyframes/FfProbe/FfProbeKeyframeExtractor.cs
@@ -11,7 +11,7 @@ namespace Jellyfin.MediaEncoding.Keyframes.FfProbe;
/// </summary>
public static class FfProbeKeyframeExtractor
{
- private const string DefaultArguments = "-v error -skip_frame nokey -show_entries format=duration -show_entries stream=duration -show_entries packet=pts_time,flags -select_streams v -of csv \"{0}\"";
+ private const string DefaultArguments = "-fflags +genpts -v error -skip_frame nokey -show_entries format=duration -show_entries stream=duration -show_entries packet=pts_time,flags -select_streams v -of csv \"{0}\"";
/// <summary>
/// Extracts the keyframes using the ffprobe executable at the specified path.
@@ -62,12 +62,17 @@ public static class FfProbeKeyframeExtractor
var rest = line[(firstComma + 1)..];
if (lineType.Equals("packet", StringComparison.OrdinalIgnoreCase))
{
- if (rest.EndsWith(",K_"))
+ // Split time and flags from the packet line. Example line: packet,7169.079000,K_
+ var secondComma = rest.IndexOf(',');
+ var ptsTime = rest[..secondComma];
+ var flags = rest[(secondComma + 1)..];
+ if (flags.StartsWith("K_"))
{
- // Trim the flags from the packet line. Example line: packet,7169.079000,K_
- var keyframe = double.Parse(rest[..^3], NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture);
- // Have to manually convert to ticks to avoid rounding errors as TimeSpan is only precise down to 1 ms when converting double.
- keyframes.Add(Convert.ToInt64(keyframe * TimeSpan.TicksPerSecond));
+ if (double.TryParse(ptsTime, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var keyframe))
+ {
+ // Have to manually convert to ticks to avoid rounding errors as TimeSpan is only precise down to 1 ms when converting double.
+ keyframes.Add(Convert.ToInt64(keyframe * TimeSpan.TicksPerSecond));
+ }
}
}
else if (lineType.Equals("stream", StringComparison.OrdinalIgnoreCase))
diff --git a/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj b/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj
index d68c6cca8..9585cb60c 100644
--- a/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj
+++ b/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj
@@ -16,12 +16,12 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
+ <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.2" />
</ItemGroup>
<ItemGroup>
diff --git a/tests/Jellyfin.Api.Tests/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandlerTests.cs
index 23c51999f..7c85ddd62 100644
--- a/tests/Jellyfin.Api.Tests/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandlerTests.cs
+++ b/tests/Jellyfin.Api.Tests/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandlerTests.cs
@@ -62,7 +62,7 @@ namespace Jellyfin.Api.Tests.Auth.DefaultAuthorizationPolicy
}
}
- private static TheoryData<string, Dictionary<string, string>> GetParts_ValidAuthHeader_Success_Data()
+ public static TheoryData<string, Dictionary<string, string>> GetParts_ValidAuthHeader_Success_Data()
{
var data = new TheoryData<string, Dictionary<string, string>>();
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index 0b9d8f7c0..a20c9690f 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -15,13 +15,16 @@
<PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.3" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.9" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
- <PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
+ <PackageReference Include="xunit" Version="2.4.2" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2" />
- <PackageReference Include="Moq" Version="4.17.2" />
+ <PackageReference Include="Moq" Version="4.18.2" />
</ItemGroup>
<!-- Code Analyzers -->
@@ -31,7 +34,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs b/tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs
index 3ae6ae5bd..e37c9d91f 100644
--- a/tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs
+++ b/tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs
@@ -192,7 +192,9 @@ namespace Jellyfin.Api.Tests.ModelBinders
await modelBinder.BindModelAsync(bindingContextMock.Object);
Assert.True(bindingContextMock.Object.Result.IsModelSet);
- Assert.Empty((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model);
+ var listResult = (IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model;
+ Assert.NotNull(listResult);
+ Assert.Empty(listResult);
}
[Fact]
@@ -220,7 +222,9 @@ namespace Jellyfin.Api.Tests.ModelBinders
await modelBinder.BindModelAsync(bindingContextMock.Object);
Assert.True(bindingContextMock.Object.Result.IsModelSet);
- Assert.Single((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model);
+ var listResult = (IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model;
+ Assert.NotNull(listResult);
+ Assert.Single(listResult);
}
}
}
diff --git a/tests/Jellyfin.Api.Tests/ModelBinders/PipeDelimitedArrayModelBinderTests.cs b/tests/Jellyfin.Api.Tests/ModelBinders/PipeDelimitedArrayModelBinderTests.cs
index 938d19a15..7c05ee036 100644
--- a/tests/Jellyfin.Api.Tests/ModelBinders/PipeDelimitedArrayModelBinderTests.cs
+++ b/tests/Jellyfin.Api.Tests/ModelBinders/PipeDelimitedArrayModelBinderTests.cs
@@ -192,7 +192,9 @@ namespace Jellyfin.Api.Tests.ModelBinders
await modelBinder.BindModelAsync(bindingContextMock.Object);
Assert.True(bindingContextMock.Object.Result.IsModelSet);
- Assert.Empty((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model);
+ var listResult = (IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model;
+ Assert.NotNull(listResult);
+ Assert.Empty(listResult);
}
[Fact]
@@ -220,7 +222,9 @@ namespace Jellyfin.Api.Tests.ModelBinders
await modelBinder.BindModelAsync(bindingContextMock.Object);
Assert.True(bindingContextMock.Object.Result.IsModelSet);
- Assert.Single((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model);
+ var listResult = (IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model;
+ Assert.NotNull(listResult);
+ Assert.Single(listResult);
}
}
}
diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
index ee51562b4..95fc1d917 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -12,11 +12,14 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
- <PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
+ <PackageReference Include="xunit" Version="2.4.2" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2" />
- <PackageReference Include="FsCheck.Xunit" Version="2.16.4" />
+ <PackageReference Include="FsCheck.Xunit" Version="2.16.5" />
</ItemGroup>
<!-- Code Analyzers -->
@@ -26,7 +29,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
index d2087b023..d95747206 100644
--- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
+++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
@@ -12,10 +12,13 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
- <PackageReference Include="Moq" Version="4.17.2" />
- <PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
+ <PackageReference Include="Moq" Version="4.18.2" />
+ <PackageReference Include="xunit" Version="2.4.2" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2" />
</ItemGroup>
@@ -26,7 +29,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
index c8c526f4d..1444d6faf 100644
--- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
+++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
@@ -7,10 +7,13 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
- <PackageReference Include="Moq" Version="4.17.2" />
- <PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
+ <PackageReference Include="Moq" Version="4.18.2" />
+ <PackageReference Include="xunit" Version="2.4.2" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2" />
</ItemGroup>
@@ -21,7 +24,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
index bd8667a84..9e3bef881 100644
--- a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
+++ b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
@@ -7,9 +7,9 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
@@ -17,7 +17,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
- <PackageReference Include="FsCheck.Xunit" Version="2.16.4" />
+ <PackageReference Include="FsCheck.Xunit" Version="2.16.5" />
</ItemGroup>
<!-- Code Analyzers -->
@@ -27,7 +27,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonStringConverterTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonStringConverterTests.cs
index 345f37cbe..77717af70 100644
--- a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonStringConverterTests.cs
+++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonStringConverterTests.cs
@@ -32,7 +32,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters
const string? input = "123";
const int output = 123;
var deserialized = JsonSerializer.Deserialize<int>(input, _jsonSerializerOptions);
- Assert.Equal(deserialized, output);
+ Assert.Equal(output, deserialized);
}
}
}
diff --git a/tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs b/tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs
index 7186cc023..903d88caa 100644
--- a/tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs
+++ b/tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs
@@ -6,6 +6,38 @@ namespace Jellyfin.Extensions.Tests
public class StringExtensionsTests
{
[Theory]
+ [InlineData("", "")] // Identity edge-case (no diactritics)
+ [InlineData("Indiana Jones", "Indiana Jones")] // Identity (no diactritics)
+ [InlineData("a\ud800b", "ab")] // Invalid UTF-16 char stripping
+ [InlineData("Jön", "Jon")] // Issue #7484
+ [InlineData("Jönssonligan", "Jonssonligan")] // Issue #7484
+ [InlineData("Kieślowski", "Kieslowski")] // Issue #7450
+ [InlineData("Cidadão Kane", "Cidadao Kane")] // Issue #7560
+ [InlineData("운명처럼 널 사랑해", "운명처럼 널 사랑해")] // Issue #6393 (Korean language support)
+ [InlineData("애타는 로맨스", "애타는 로맨스")] // Issue #6393
+ public void RemoveDiacritics_ValidInput_Corrects(string input, string expectedResult)
+ {
+ string result = input.RemoveDiacritics();
+ Assert.Equal(expectedResult, result);
+ }
+
+ [Theory]
+ [InlineData("", false)] // Identity edge-case (no diactritics)
+ [InlineData("Indiana Jones", false)] // Identity (no diactritics)
+ [InlineData("a\ud800b", true)] // Invalid UTF-16 char stripping
+ [InlineData("Jön", true)] // Issue #7484
+ [InlineData("Jönssonligan", true)] // Issue #7484
+ [InlineData("Kieślowski", true)] // Issue #7450
+ [InlineData("Cidadão Kane", true)] // Issue #7560
+ [InlineData("운명처럼 널 사랑해", false)] // Issue #6393 (Korean language support)
+ [InlineData("애타는 로맨스", false)] // Issue #6393
+ public void HasDiacritics_ValidInput_Corrects(string input, bool expectedResult)
+ {
+ bool result = input.HasDiacritics();
+ Assert.Equal(expectedResult, result);
+ }
+
+ [Theory]
[InlineData("", '_', 0)]
[InlineData("___", '_', 3)]
[InlineData("test\x00", '\x00', 1)]
diff --git a/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj b/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj
index cc36dc4fa..83ea1907c 100644
--- a/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj
@@ -7,9 +7,9 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
- <PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
+ <PackageReference Include="xunit" Version="2.4.2" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
@@ -26,7 +26,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
diff --git a/tests/Jellyfin.MediaEncoding.Hls.Tests/Playlist/DynamicHlsPlaylistGeneratorTests.cs b/tests/Jellyfin.MediaEncoding.Hls.Tests/Playlist/DynamicHlsPlaylistGeneratorTests.cs
index 79648c4f6..bbacdcd62 100644
--- a/tests/Jellyfin.MediaEncoding.Hls.Tests/Playlist/DynamicHlsPlaylistGeneratorTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Hls.Tests/Playlist/DynamicHlsPlaylistGeneratorTests.cs
@@ -53,7 +53,7 @@ namespace Jellyfin.MediaEncoding.Hls.Tests.Playlist
Assert.False(DynamicHlsPlaylistGenerator.IsExtractionAllowedForFile(filePath, allowedExtensions));
}
- private static TheoryData<int, long, double[]> ComputeEqualLengthSegments_Valid_Success_Data()
+ public static TheoryData<int, long, double[]> ComputeEqualLengthSegments_Valid_Success_Data()
{
var data = new TheoryData<int, long, double[]>
{
@@ -67,7 +67,7 @@ namespace Jellyfin.MediaEncoding.Hls.Tests.Playlist
return data;
}
- private static TheoryData<KeyframeData, int, double[]> ComputeSegments_Valid_Success_Data()
+ public static TheoryData<KeyframeData, int, double[]> ComputeSegments_Valid_Success_Data()
{
var data = new TheoryData<KeyframeData, int, double[]>
{
diff --git a/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj
index de1fcab59..84a069424 100644
--- a/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj
@@ -8,9 +8,9 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
- <PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
+ <PackageReference Include="xunit" Version="2.4.2" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
@@ -27,7 +27,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
index e186a488a..4cff2143c 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
@@ -22,10 +22,13 @@
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
<PackageReference Include="coverlet.collector" Version="3.1.2" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
- <PackageReference Include="Moq" Version="4.17.2" />
- <PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
+ <PackageReference Include="Moq" Version="4.18.2" />
+ <PackageReference Include="xunit" Version="2.4.2" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
</ItemGroup>
<!-- Code Analyzers -->
@@ -35,7 +38,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs
index 53e1550ed..13cfe885f 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs
@@ -75,6 +75,14 @@ namespace Jellyfin.MediaEncoding.Tests.Probing
Assert.Equal(1, res.VideoStream.RefFrames);
Assert.Equal("1/1000", res.VideoStream.TimeBase);
Assert.Equal(MediaStreamType.Video, res.VideoStream.Type);
+ Assert.Equal(1, res.VideoStream.DvVersionMajor);
+ Assert.Equal(0, res.VideoStream.DvVersionMinor);
+ Assert.Equal(5, res.VideoStream.DvProfile);
+ Assert.Equal(6, res.VideoStream.DvLevel);
+ Assert.Equal(1, res.VideoStream.RpuPresentFlag);
+ Assert.Equal(0, res.VideoStream.ElPresentFlag);
+ Assert.Equal(1, res.VideoStream.BlPresentFlag);
+ Assert.Equal(0, res.VideoStream.DvBlSignalCompatibilityId);
Assert.Empty(res.Chapters);
Assert.Equal("Just color bars", res.Overview);
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs
index 3775555de..fe0d7fc90 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs
@@ -1,7 +1,6 @@
using System;
using System.Globalization;
using System.IO;
-using System.Threading;
using MediaBrowser.MediaEncoding.Subtitles;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
@@ -15,7 +14,7 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests
{
using (var stream = File.OpenRead("Test Data/example.ass"))
{
- var parsed = new AssParser(new NullLogger<AssParser>()).Parse(stream, CancellationToken.None);
+ var parsed = new SubtitleEditParser(new NullLogger<SubtitleEditParser>()).Parse(stream, "ass");
Assert.Single(parsed.TrackEvents);
var trackEvent = parsed.TrackEvents[0];
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs
index c07c9ea7d..2aebee556 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs
@@ -1,7 +1,6 @@
using System;
using System.Globalization;
using System.IO;
-using System.Threading;
using MediaBrowser.MediaEncoding.Subtitles;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
@@ -15,7 +14,7 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests
{
using (var stream = File.OpenRead("Test Data/example.srt"))
{
- var parsed = new SrtParser(new NullLogger<SrtParser>()).Parse(stream, CancellationToken.None);
+ var parsed = new SubtitleEditParser(new NullLogger<SubtitleEditParser>()).Parse(stream, "srt");
Assert.Equal(2, parsed.TrackEvents.Count);
var trackEvent1 = parsed.TrackEvents[0];
@@ -37,7 +36,7 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests
{
using (var stream = File.OpenRead("Test Data/example2.srt"))
{
- var parsed = new SrtParser(new NullLogger<SrtParser>()).Parse(stream, CancellationToken.None);
+ var parsed = new SubtitleEditParser(new NullLogger<SubtitleEditParser>()).Parse(stream, "srt");
Assert.Equal(2, parsed.TrackEvents.Count);
var trackEvent1 = parsed.TrackEvents[0];
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs
index 56649db8f..6abf2d26c 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
-using System.Threading;
using MediaBrowser.MediaEncoding.Subtitles;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging.Abstractions;
@@ -13,7 +12,7 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests
{
public class SsaParserTests
{
- private readonly SsaParser _parser = new SsaParser(new NullLogger<AssParser>());
+ private readonly SubtitleEditParser _parser = new SubtitleEditParser(new NullLogger<SubtitleEditParser>());
[Theory]
[MemberData(nameof(Parse_MultipleDialogues_TestData))]
@@ -21,7 +20,7 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests
{
using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(ssa)))
{
- SubtitleTrackInfo subtitleTrackInfo = _parser.Parse(stream, CancellationToken.None);
+ SubtitleTrackInfo subtitleTrackInfo = _parser.Parse(stream, "ssa");
Assert.Equal(expectedSubtitleTrackEvents.Count, subtitleTrackInfo.TrackEvents.Count);
@@ -76,7 +75,7 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests
{
using (var stream = File.OpenRead("Test Data/example.ssa"))
{
- var parsed = _parser.Parse(stream, CancellationToken.None);
+ var parsed = _parser.Parse(stream, "ssa");
Assert.Single(parsed.TrackEvents);
var trackEvent = parsed.TrackEvents[0];
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SubtitleEncoderTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SubtitleEncoderTests.cs
index 639c364df..243127438 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SubtitleEncoderTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SubtitleEncoderTests.cs
@@ -12,7 +12,7 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests
{
public class SubtitleEncoderTests
{
- internal static TheoryData<MediaSourceInfo, MediaStream, SubtitleEncoder.SubtitleInfo> GetReadableFile_Valid_TestData()
+ public static TheoryData<MediaSourceInfo, MediaStream, SubtitleEncoder.SubtitleInfo> GetReadableFile_Valid_TestData()
{
var data = new TheoryData<MediaSourceInfo, MediaStream, SubtitleEncoder.SubtitleInfo>();
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_metadata.json b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_metadata.json
index 720fc5c8f..519d81179 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_metadata.json
+++ b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_metadata.json
@@ -47,7 +47,20 @@
"tags": {
"ENCODER": "Lavc57.107.100 libx264",
"DURATION": "00:00:01.000000000"
- }
+ },
+ "side_data_list": [
+ {
+ "side_data_type": "DOVI configuration record",
+ "dv_version_major": 1,
+ "dv_version_minor": 0,
+ "dv_profile": 5,
+ "dv_level": 6,
+ "rpu_present_flag": 1,
+ "el_present_flag": 0,
+ "bl_present_flag": 1,
+ "dv_bl_signal_compatibility_id": 0
+ }
+ ]
}
],
"chapters": [
diff --git a/tests/Jellyfin.Model.Tests/Cryptography/PasswordHashTests.cs b/tests/Jellyfin.Model.Tests/Cryptography/PasswordHashTests.cs
index 6948280a3..162f53e56 100644
--- a/tests/Jellyfin.Model.Tests/Cryptography/PasswordHashTests.cs
+++ b/tests/Jellyfin.Model.Tests/Cryptography/PasswordHashTests.cs
@@ -152,9 +152,9 @@ namespace Jellyfin.Model.Tests.Cryptography
[InlineData("$PBKDF2$$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Empty segment
[InlineData("$PBKDF2$iterations=1000$$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Empty salt segment
[InlineData("$PBKDF2$iterations=1000$69F420$")] // Empty hash segment
- [InlineData("$PBKDF2$=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter
- [InlineData("$PBKDF2$=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter
- [InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter
+ [InlineData("$PBKDF2$=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parameter
+ [InlineData("$PBKDF2$=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parameter
+ [InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parameter
[InlineData("$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Ends on $
[InlineData("$PBKDF2$iterations=1000$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Extra segment
[InlineData("$PBKDF2$iterations=1000$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$anotherone")] // Extra segment
diff --git a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
index 4748f3497..9baf6877d 100644
--- a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
+++ b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
@@ -27,7 +27,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
- [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+ [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
[InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// Firefox
@@ -38,7 +38,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
- [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+ [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
[InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// Safari
@@ -89,7 +89,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Chrome-NoHLS", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome-NoHLS", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")]
[InlineData("Chrome-NoHLS", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")]
- [InlineData("Chrome-NoHLS", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+ [InlineData("Chrome-NoHLS", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
[InlineData("Chrome-NoHLS", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome-NoHLS", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// TranscodeMedia
@@ -131,6 +131,37 @@ namespace Jellyfin.Model.Tests
[InlineData("Null", "mkv-vp9-aac-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
[InlineData("Null", "mkv-vp9-ac3-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
[InlineData("Null", "mkv-vp9-vorbis-vtt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
+ // AndroidTV
+ [InlineData("AndroidTVExoPlayer", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("AndroidTVExoPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("AndroidTVExoPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("AndroidTVExoPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay)]
+ [InlineData("AndroidTVExoPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)]
+ [InlineData("AndroidTVExoPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("AndroidTVExoPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("AndroidTVExoPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
+ // Tizen 3 Stereo
+ [InlineData("Tizen3-stereo", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen3-stereo", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen3-stereo", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen3-stereo", "mp4-h264-dts-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen3-stereo", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen3-stereo", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen3-stereo", "mp4-hevc-truehd-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
+ [InlineData("Tizen3-stereo", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen3-stereo", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen3-stereo", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)]
+ // Tizen 4 4K 5.1
+ [InlineData("Tizen4-4K-5.1", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen4-4K-5.1", "mp4-h264-dts-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
+ [InlineData("Tizen4-4K-5.1", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen4-4K-5.1", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen4-4K-5.1", "mp4-hevc-truehd-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
+ [InlineData("Tizen4-4K-5.1", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen4-4K-5.1", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen4-4K-5.1", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)]
public async Task BuildVideoItemSimple(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "")
{
var options = await GetVideoOptions(deviceName, mediaSource);
@@ -146,7 +177,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
- [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+ [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
[InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// Firefox
@@ -156,7 +187,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
- [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+ [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
[InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// Safari
@@ -198,6 +229,37 @@ namespace Jellyfin.Model.Tests
[InlineData("JellyfinMediaPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
[InlineData("JellyfinMediaPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450
[InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)] // #6450
+ // AndroidTV
+ [InlineData("AndroidTVExoPlayer", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("AndroidTVExoPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("AndroidTVExoPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("AndroidTVExoPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay)]
+ [InlineData("AndroidTVExoPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)]
+ [InlineData("AndroidTVExoPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("AndroidTVExoPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("AndroidTVExoPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
+ // Tizen 3 Stereo
+ [InlineData("Tizen3-stereo", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen3-stereo", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen3-stereo", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen3-stereo", "mp4-h264-dts-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen3-stereo", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen3-stereo", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen3-stereo", "mp4-hevc-truehd-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
+ [InlineData("Tizen3-stereo", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen3-stereo", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen3-stereo", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)]
+ // Tizen 4 4K 5.1
+ [InlineData("Tizen4-4K-5.1", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen4-4K-5.1", "mp4-h264-dts-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
+ [InlineData("Tizen4-4K-5.1", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen4-4K-5.1", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen4-4K-5.1", "mp4-hevc-truehd-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
+ [InlineData("Tizen4-4K-5.1", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen4-4K-5.1", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)]
+ [InlineData("Tizen4-4K-5.1", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)]
public async Task BuildVideoItemWithFirstExplicitStream(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "")
{
var options = await GetVideoOptions(deviceName, mediaSource);
@@ -223,12 +285,26 @@ namespace Jellyfin.Model.Tests
// RokuSSPlus
[InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
[InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+ // no streams
+ [InlineData("Chrome", "no-streams", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] // #6450
+ // AndroidTV
+ [InlineData("AndroidTVExoPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
+ [InlineData("AndroidTVExoPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
+ // Tizen 3 Stereo
+ [InlineData("Tizen3-stereo", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
+ [InlineData("Tizen3-stereo", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
+ // Tizen 4 4K 5.1
+ [InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
+ [InlineData("Tizen4-4K-5.1", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
public async Task BuildVideoItemWithDirectPlayExplicitStreams(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "")
{
var options = await GetVideoOptions(deviceName, mediaSource);
var streamCount = options.MediaSources[0].MediaStreams.Count;
- options.AudioStreamIndex = streamCount - 2;
- options.SubtitleStreamIndex = streamCount - 1;
+ if (streamCount > 0)
+ {
+ options.AudioStreamIndex = streamCount - 2;
+ options.SubtitleStreamIndex = streamCount - 1;
+ }
var streamInfo = BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol);
Assert.Equal(streamInfo?.AudioStreamIndex, options.AudioStreamIndex);
@@ -262,23 +338,23 @@ namespace Jellyfin.Model.Tests
Assert.NotNull(mediaSource);
var videoStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Video);
var audioStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio);
- // TODO: check AudioStreamIndex vs options.AudioStreamIndex
+ // TODO: Check AudioStreamIndex vs options.AudioStreamIndex
var inputAudioStream = mediaSource.GetDefaultAudioStream(audioStreamIndexInput ?? mediaSource.DefaultAudioStreamIndex);
var uri = ParseUri(val);
if (playMethod == PlayMethod.DirectPlay)
{
- // check expected container
+ // Check expected container
var containers = ContainerProfile.SplitValue(mediaSource.Container);
- // TODO: test transcode too
+ // TODO: Test transcode too
// Assert.Contains(uri.Extension, containers);
- // check expected video codec (1)
+ // Check expected video codec (1)
Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec);
Assert.Single(val.TargetVideoCodec);
- // check expected audio codecs (1)
+ // Check expected audio codecs (1)
Assert.Contains(targetAudioStream.Codec, val.TargetAudioCodec);
Assert.Single(val.TargetAudioCodec);
// Assert.Single(val.AudioCodecs);
@@ -294,7 +370,7 @@ namespace Jellyfin.Model.Tests
Assert.NotEmpty(val.VideoCodecs);
Assert.NotEmpty(val.AudioCodecs);
- // check expected container (todo: this could be a test param)
+ // Check expected container (todo: this could be a test param)
if (transcodeProtocol == "http")
{
// Assert.Equal("webm", val.Container);
@@ -327,32 +403,39 @@ namespace Jellyfin.Model.Tests
stream => Assert.DoesNotContain(stream.Codec, val.VideoCodecs));
}
- // todo: fill out tests here
+ // TODO: Fill out tests here
}
// DirectStream and Remux
else
{
- // check expected video codec (1)
+ // Check expected video codec (1)
Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec);
Assert.Single(val.TargetVideoCodec);
if (transcodeMode == "DirectStream")
{
+ // Check expected audio codecs (1)
if (!targetAudioStream.IsExternal)
{
- // check expected audio codecs (1)
- Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs);
+ if (val.TranscodeReasons.HasFlag(TranscodeReason.ContainerNotSupported))
+ {
+ Assert.Contains(targetAudioStream.Codec, val.AudioCodecs);
+ }
+ else
+ {
+ Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs);
+ }
}
}
else if (transcodeMode == "Remux")
{
- // check expected audio codecs (1)
+ // Check expected audio codecs (1)
Assert.Contains(targetAudioStream.Codec, val.AudioCodecs);
Assert.Single(val.AudioCodecs);
}
- // video details
+ // Video details
var videoStream = targetVideoStream;
Assert.False(val.EstimateContentLength);
Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo);
@@ -361,10 +444,10 @@ namespace Jellyfin.Model.Tests
Assert.Equal(videoStream.BitDepth, val.TargetVideoBitDepth);
Assert.InRange(val.VideoBitrate.GetValueOrDefault(), videoStream.BitRate.GetValueOrDefault(), int.MaxValue);
- // audio codec not supported
+ // Audio codec not supported
if ((why & TranscodeReason.AudioCodecNotSupported) != 0)
{
- // audio stream specified
+ // Audio stream specified
if (options.AudioStreamIndex >= 0)
{
// TODO:fixme
@@ -374,10 +457,10 @@ namespace Jellyfin.Model.Tests
}
}
- // audio stream not specified
+ // Audio stream not specified
else
{
- // TODO:fixme
+ // TODO: Fixme
Assert.All(audioStreams, stream =>
{
if (!stream.IsExternal)
diff --git a/tests/Jellyfin.Model.Tests/Drawing/ImageFormatExtensionsTests.cs b/tests/Jellyfin.Model.Tests/Drawing/ImageFormatExtensionsTests.cs
index 7c3a7ff6c..a5bdb42d8 100644
--- a/tests/Jellyfin.Model.Tests/Drawing/ImageFormatExtensionsTests.cs
+++ b/tests/Jellyfin.Model.Tests/Drawing/ImageFormatExtensionsTests.cs
@@ -7,7 +7,7 @@ namespace Jellyfin.Model.Drawing;
public static class ImageFormatExtensionsTests
{
- private static TheoryData<ImageFormat> GetAllImageFormats()
+ public static TheoryData<ImageFormat> GetAllImageFormats()
{
var theoryTypes = new TheoryData<ImageFormat>();
foreach (var x in Enum.GetValues<ImageFormat>())
diff --git a/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs b/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs
index 9fcf8189f..80c38affe 100644
--- a/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs
+++ b/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs
@@ -109,26 +109,37 @@ namespace Jellyfin.Model.Tests.Entities
[InlineData(null, null, false, null)]
[InlineData(null, 0, false, null)]
[InlineData(0, null, false, null)]
- [InlineData(640, 480, false, "480p")]
- [InlineData(640, 480, true, "480i")]
- [InlineData(720, 576, false, "576p")]
- [InlineData(720, 576, true, "576i")]
+ [InlineData(256, 144, false, "144p")]
+ [InlineData(256, 144, true, "144i")]
+ [InlineData(426, 240, false, "240p")]
+ [InlineData(426, 240, true, "240i")]
+ [InlineData(640, 360, false, "360p")]
+ [InlineData(640, 360, true, "360i")]
+ [InlineData(854, 480, false, "480p")]
+ [InlineData(854, 480, true, "480i")]
[InlineData(960, 540, false, "540p")]
[InlineData(960, 540, true, "540i")]
+ [InlineData(1024, 576, false, "576p")]
+ [InlineData(1024, 576, true, "576i")]
[InlineData(1280, 720, false, "720p")]
[InlineData(1280, 720, true, "720i")]
- [InlineData(1920, 1080, false, "1080p")]
- [InlineData(1920, 1080, true, "1080i")]
+ [InlineData(2560, 1080, false, "1080p")]
+ [InlineData(2560, 1080, true, "1080i")]
[InlineData(4096, 3072, false, "4K")]
[InlineData(8192, 6144, false, "8K")]
- [InlineData(512, 384, false, "480p")]
- [InlineData(576, 336, false, "480p")]
- [InlineData(624, 352, false, "480p")]
- [InlineData(640, 352, false, "480p")]
- [InlineData(704, 396, false, "480p")]
- [InlineData(720, 404, false, "480p")]
+ [InlineData(512, 384, false, "384p")]
+ [InlineData(576, 336, false, "360p")]
+ [InlineData(576, 336, true, "360i")]
+ [InlineData(624, 352, false, "360p")]
+ [InlineData(640, 352, false, "360p")]
+ [InlineData(640, 480, false, "480p")]
+ [InlineData(704, 396, false, "404p")]
+ [InlineData(720, 404, false, "404p")]
[InlineData(720, 480, false, "480p")]
+ [InlineData(720, 576, false, "576p")]
[InlineData(768, 576, false, "576p")]
+ [InlineData(960, 544, false, "540p")]
+ [InlineData(960, 544, true, "540i")]
[InlineData(960, 720, false, "720p")]
[InlineData(1280, 528, false, "720p")]
[InlineData(1280, 532, false, "720p")]
@@ -140,6 +151,11 @@ namespace Jellyfin.Model.Tests.Entities
[InlineData(1280, 696, false, "720p")]
[InlineData(1280, 716, false, "720p")]
[InlineData(1280, 718, false, "720p")]
+ [InlineData(1920, 1080, false, "1080p")]
+ [InlineData(1440, 1070, false, "1080p")]
+ [InlineData(1440, 1072, false, "1080p")]
+ [InlineData(1440, 1080, false, "1080p")]
+ [InlineData(1440, 1440, false, "1080p")]
[InlineData(1912, 792, false, "1080p")]
[InlineData(1916, 1076, false, "1080p")]
[InlineData(1918, 1080, false, "1080p")]
@@ -153,14 +169,16 @@ namespace Jellyfin.Model.Tests.Entities
[InlineData(1920, 960, false, "1080p")]
[InlineData(1920, 1024, false, "1080p")]
[InlineData(1920, 1040, false, "1080p")]
+ [InlineData(1920, 1070, false, "1080p")]
[InlineData(1920, 1072, false, "1080p")]
- [InlineData(1440, 1072, false, "1080p")]
- [InlineData(1440, 1080, false, "1080p")]
+ [InlineData(1920, 1440, false, "1080p")]
[InlineData(3840, 1600, false, "4K")]
[InlineData(3840, 1606, false, "4K")]
[InlineData(3840, 1608, false, "4K")]
[InlineData(3840, 2160, false, "4K")]
+ [InlineData(4090, 3070, false, "4K")]
[InlineData(7680, 4320, false, "8K")]
+ [InlineData(8190, 6140, false, "8K")]
public void GetResolutionText_Valid(int? width, int? height, bool interlaced, string expected)
{
var mediaStream = new MediaStream()
diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
index 5fdb22546..fbcaa66f4 100644
--- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
+++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
@@ -7,12 +7,15 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
- <PackageReference Include="Moq" Version="4.17.2" />
- <PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
+ <PackageReference Include="Moq" Version="4.18.2" />
+ <PackageReference Include="xunit" Version="2.4.2" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2" />
- <PackageReference Include="FsCheck.Xunit" Version="2.16.4" />
+ <PackageReference Include="FsCheck.Xunit" Version="2.16.5" />
</ItemGroup>
<ItemGroup>
@@ -28,7 +31,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidTVExoPlayer.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidTVExoPlayer.json
new file mode 100644
index 000000000..3d3968268
--- /dev/null
+++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidTVExoPlayer.json
@@ -0,0 +1,216 @@
+{
+ "Name": "Jellyfin AndroidTV-ExoPlayer",
+ "EnableAlbumArtInDidl": false,
+ "EnableSingleAlbumArtLimit": false,
+ "EnableSingleSubtitleLimit": false,
+ "SupportedMediaTypes": "Audio,Photo,Video",
+ "MaxAlbumArtWidth": 0,
+ "MaxAlbumArtHeight": 0,
+ "MaxStreamingBitrate": 120000000,
+ "MaxStaticBitrate": 100000000,
+ "MusicStreamingTranscodingBitrate": 192000,
+ "TimelineOffsetSeconds": 0,
+ "RequiresPlainVideoItems": false,
+ "RequiresPlainFolders": false,
+ "EnableMSMediaReceiverRegistrar": false,
+ "IgnoreTranscodeByteRangeRequests": false,
+ "DirectPlayProfiles": [
+ {
+ "Container": "m4v,mov,xvid,vob,mkv,wmv,asf,ogm,ogv,mp4,webm",
+ "AudioCodec": "aac,mp3,mp2,aac_latm,alac,ac3,eac3,dca,dts,mlp,truehd,pcm_alaw,pcm_mulaw",
+ "VideoCodec": "h264,hevc,vp8,vp9,mpeg,mpeg2video",
+ "Type": "Video",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "aac,mp3,mp2,aac_latm,alac,ac3,eac3,dca,dts,mlp,truehd,pcm_alaw,pcm_mulaw,,pa,flac,wav,wma,ogg,oga,webma,ape,opus",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "jpg,jpeg,png,gif,web",
+ "Type": "Photo",
+ "$type": "DirectPlayProfile"
+ }
+ ],
+ "CodecProfiles": [
+ {
+ "Type": "Video",
+ "Conditions": [
+ {
+ "Condition": "EqualsAny",
+ "Property": "VideoProfile",
+ "Value": "high|main|baseline|constrained baseline",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ },
+ {
+ "Condition": "LessThanEqual",
+ "Property": "VideoLevel",
+ "Value": "51",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ }
+ ],
+ "Codec": "h264",
+ "$type": "CodecProfile"
+ },
+ {
+ "Type": "Video",
+ "Conditions": [
+ {
+ "Condition": "LessThanEqual",
+ "Property": "RefFrames",
+ "Value": "12",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ }
+ ],
+ "ApplyConditions": [
+ {
+ "Condition": "GreaterThanEqual",
+ "Property": "Width",
+ "Value": "1200",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ }
+ ],
+ "Codec": "h264",
+ "$type": "CodecProfile"
+ },
+ {
+ "Type": "Video",
+ "Conditions": [
+ {
+ "Condition": "LessThanEqual",
+ "Property": "RefFrames",
+ "Value": "4",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ }
+ ],
+ "ApplyConditions": [
+ {
+ "Condition": "GreaterThanEqual",
+ "Property": "Width",
+ "Value": "1900",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ }
+ ],
+ "Codec": "h264",
+ "$type": "CodecProfile"
+ },
+ {
+ "Type": "VideoAudio",
+ "Conditions": [
+ {
+ "Condition": "LessThanEqual",
+ "Property": "AudioChannels",
+ "Value": "6",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ }
+ ],
+ "$type": "CodecProfile"
+ }
+ ],
+ "TranscodingProfiles": [
+ {
+ "Container": "ts",
+ "Type": "Video",
+ "VideoCodec": "h264",
+ "AudioCodec": "aac,mp3",
+ "Protocol": "hls",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Streaming",
+ "EnableSubtitlesInManifest": false,
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "mp3",
+ "Type": "Audio",
+ "AudioCodec": "mp3",
+ "Protocol": "http",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Streaming",
+ "EnableSubtitlesInManifest": false,
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "$type": "TranscodingProfile"
+ }
+ ],
+ "SubtitleProfiles": [
+ {
+ "Format": "srt",
+ "Method": "Embed",
+ "$type": "SubtitleProfile"
+ },
+ {
+ "Format": "srt",
+ "Method": "External",
+ "$type": "SubtitleProfile"
+ },
+ {
+ "Format": "subrip",
+ "Method": "Embed",
+ "$type": "SubtitleProfile"
+ },
+ {
+ "Format": "subrip",
+ "Method": "External",
+ "$type": "SubtitleProfile"
+ },
+ {
+ "Format": "ass",
+ "Method": "Encode",
+ "$type": "SubtitleProfile"
+ },
+ {
+ "Format": "ssa",
+ "Method": "Encode",
+ "$type": "SubtitleProfile"
+ },
+ {
+ "Format": "pgs",
+ "Method": "Encode",
+ "$type": "SubtitleProfile"
+ },
+ {
+ "Format": "pgssub",
+ "Method": "Encode",
+ "$type": "SubtitleProfile"
+ },
+ {
+ "Format": "dvdsub",
+ "Method": "Encode",
+ "$type": "SubtitleProfile"
+ },
+ {
+ "Format": "vtt",
+ "Method": "Embed",
+ "$type": "SubtitleProfile"
+ },
+ {
+ "Format": "sub",
+ "Method": "Embed",
+ "$type": "SubtitleProfile"
+ },
+ {
+ "Format": "idx",
+ "Method": "Embed",
+ "$type": "SubtitleProfile"
+ }
+ ],
+ "$type": "DeviceProfile"
+}
diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json
new file mode 100644
index 000000000..53637b793
--- /dev/null
+++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json
@@ -0,0 +1,526 @@
+{
+ "Name": "Jellyfin Tizen 3 Stereo",
+ "EnableAlbumArtInDidl": false,
+ "EnableSingleAlbumArtLimit": false,
+ "EnableSingleSubtitleLimit": false,
+ "SupportedMediaTypes": "Audio,Photo,Video",
+ "MaxAlbumArtWidth": 0,
+ "MaxAlbumArtHeight": 0,
+ "MaxStreamingBitrate": 120000000,
+ "MaxStaticBitrate": 100000000,
+ "MusicStreamingTranscodingBitrate": 384000,
+ "TimelineOffsetSeconds": 0,
+ "RequiresPlainVideoItems": false,
+ "RequiresPlainFolders": false,
+ "EnableMSMediaReceiverRegistrar": false,
+ "IgnoreTranscodeByteRangeRequests": false,
+ "DirectPlayProfiles": [
+ {
+ "Container": "webm",
+ "AudioCodec": "vorbis,opus",
+ "VideoCodec": "vp8,vp9",
+ "Type": "Video",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "mp4,m4v",
+ "AudioCodec": "aac,mp3,ac3,eac3,mp2,dca,dts,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
+ "VideoCodec": "h264,hevc,mpeg2video,vc1,msmpeg4v2,vp8,vp9",
+ "Type": "Video",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "mkv",
+ "AudioCodec": "aac,mp3,ac3,eac3,mp2,dca,dts,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
+ "VideoCodec": "h264,hevc,mpeg2video,vc1,msmpeg4v2,vp8,vp9",
+ "Type": "Video",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "m2ts",
+ "AudioCodec": "aac,mp3,ac3,eac3,mp2,dca,dts,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
+ "VideoCodec": "h264,vc1,mpeg2video",
+ "Type": "Video",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "wmv",
+ "AudioCodec": "wma",
+ "VideoCodec": "wmv,vc1",
+ "Type": "Video",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "ts,mpegts",
+ "AudioCodec": "aac,mp3,ac3,eac3,mp2,dca,dts,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
+ "VideoCodec": "h264,hevc,vc1,mpeg2video",
+ "Type": "Video",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "asf",
+ "AudioCodec": "wma",
+ "VideoCodec": "wmv,vc1",
+ "Type": "Video",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "avi",
+ "AudioCodec": "aac,mp3,ac3,eac3,mp2,dca,dts,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
+ "VideoCodec": "",
+ "Type": "Video",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "mpg,mpeg,flv,3gp,mts,trp,vob,vro",
+ "AudioCodec": "aac,mp3,ac3,eac3,mp2,dca,dts,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
+ "VideoCodec": "",
+ "Type": "Video",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "mov",
+ "AudioCodec": "aac,mp3,ac3,eac3,mp2,dca,dts,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
+ "VideoCodec": "h264",
+ "Type": "Video",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "opus",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "webm",
+ "AudioCodec": "opus",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "mp3",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "aac",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "m4a",
+ "AudioCodec": "aac",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "m4b",
+ "AudioCodec": "aac",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "flac",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "webma",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "webm",
+ "AudioCodec": "webma",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "wma",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "wav",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "ogg",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ }
+ ],
+ "TranscodingProfiles": [
+ {
+ "Container": "aac",
+ "Type": "Audio",
+ "AudioCodec": "aac",
+ "Protocol": "hls",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Streaming",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "2",
+ "MinSegments": 1,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": true,
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "aac",
+ "Type": "Audio",
+ "AudioCodec": "aac",
+ "Protocol": "http",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Streaming",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "2",
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "mp3",
+ "Type": "Audio",
+ "AudioCodec": "mp3",
+ "Protocol": "http",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Streaming",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "2",
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "opus",
+ "Type": "Audio",
+ "AudioCodec": "opus",
+ "Protocol": "http",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Streaming",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "2",
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "wav",
+ "Type": "Audio",
+ "AudioCodec": "wav",
+ "Protocol": "http",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Streaming",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "2",
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "opus",
+ "Type": "Audio",
+ "AudioCodec": "opus",
+ "Protocol": "http",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Static",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "2",
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "mp3",
+ "Type": "Audio",
+ "AudioCodec": "mp3",
+ "Protocol": "http",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Static",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "2",
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "aac",
+ "Type": "Audio",
+ "AudioCodec": "aac",
+ "Protocol": "http",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Static",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "2",
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "wav",
+ "Type": "Audio",
+ "AudioCodec": "wav",
+ "Protocol": "http",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Static",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "2",
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "mkv",
+ "Type": "Video",
+ "VideoCodec": "h264,hevc,mpeg2video,vc1,msmpeg4v2,vp8,vp9",
+ "AudioCodec": "aac,mp3,ac3,eac3,mp2,dca,dts,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
+ "Protocol": "",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": true,
+ "Context": "Static",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "2",
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "Conditions": [
+ {
+ "Condition": "LessThanEqual",
+ "Property": "Width",
+ "Value": "1920",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ }
+ ],
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "ts",
+ "Type": "Video",
+ "VideoCodec": "h264,hevc",
+ "AudioCodec": "aac,mp3,ac3,eac3,opus",
+ "Protocol": "hls",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Streaming",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "2",
+ "MinSegments": 1,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "Conditions": [
+ {
+ "Condition": "LessThanEqual",
+ "Property": "Width",
+ "Value": "1920",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ }
+ ],
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "webm",
+ "Type": "Video",
+ "VideoCodec": "vp8,vp9,vpx",
+ "AudioCodec": "vorbis,opus",
+ "Protocol": "http",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Streaming",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "2",
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "Conditions": [
+ {
+ "Condition": "LessThanEqual",
+ "Property": "Width",
+ "Value": "1920",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ }
+ ],
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "mp4",
+ "Type": "Video",
+ "VideoCodec": "h264",
+ "AudioCodec": "aac,mp3,ac3,eac3,mp2,dca,dts,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
+ "Protocol": "http",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Static",
+ "EnableSubtitlesInManifest": false,
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "Conditions": [
+ {
+ "Condition": "LessThanEqual",
+ "Property": "Width",
+ "Value": "1920",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ }
+ ],
+ "$type": "TranscodingProfile"
+ }
+ ],
+ "CodecProfiles": [
+ {
+ "Type": "Video",
+ "Conditions": [
+ {
+ "Condition": "NotEquals",
+ "Property": "IsAnamorphic",
+ "Value": "true",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ },
+ {
+ "Condition": "EqualsAny",
+ "Property": "VideoProfile",
+ "Value": "high|main|baseline|constrained baseline|high 10",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ },
+ {
+ "Condition": "LessThanEqual",
+ "Property": "VideoLevel",
+ "Value": "52",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ },
+ {
+ "Condition": "LessThanEqual",
+ "Property": "VideoBitrate",
+ "Value": "20000000",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ }
+ ],
+ "Codec": "h264",
+ "$type": "CodecProfile"
+ },
+ {
+ "Type": "Video",
+ "Conditions": [
+ {
+ "Condition": "NotEquals",
+ "Property": "IsAnamorphic",
+ "Value": "true",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ },
+ {
+ "Condition": "EqualsAny",
+ "Property": "VideoProfile",
+ "Value": "main|main 10",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ },
+ {
+ "Condition": "LessThanEqual",
+ "Property": "VideoLevel",
+ "Value": "183",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ },
+ {
+ "Condition": "LessThanEqual",
+ "Property": "VideoBitrate",
+ "Value": "20000000",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ }
+ ],
+ "Codec": "hevc",
+ "$type": "CodecProfile"
+ },
+ {
+ "Type": "Video",
+ "Conditions": [
+ {
+ "Condition": "LessThanEqual",
+ "Property": "VideoBitrate",
+ "Value": "20000000",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ }
+ ],
+ "$type": "CodecProfile"
+ }
+ ],
+ "ResponseProfiles": [
+ {
+ "Container": "m4v",
+ "Type": "Video",
+ "MimeType": "video/mp4",
+ "$type": "ResponseProfile"
+ }
+ ],
+ "SubtitleProfiles": [
+ {
+ "Format": "vtt",
+ "Method": "External",
+ "$type": "SubtitleProfile"
+ },
+ {
+ "Format": "ass",
+ "Method": "External",
+ "$type": "SubtitleProfile"
+ },
+ {
+ "Format": "ssa",
+ "Method": "External",
+ "$type": "SubtitleProfile"
+ }
+ ],
+ "$type": "DeviceProfile"
+}
diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json
new file mode 100644
index 000000000..d3ef22c25
--- /dev/null
+++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json
@@ -0,0 +1,499 @@
+{
+ "Name": "Jellyfin Tizen 4 4K 5.1",
+ "EnableAlbumArtInDidl": false,
+ "EnableSingleAlbumArtLimit": false,
+ "EnableSingleSubtitleLimit": false,
+ "SupportedMediaTypes": "Audio,Photo,Video",
+ "MaxAlbumArtWidth": 0,
+ "MaxAlbumArtHeight": 0,
+ "MaxStreamingBitrate": 120000000,
+ "MaxStaticBitrate": 100000000,
+ "MusicStreamingTranscodingBitrate": 384000,
+ "TimelineOffsetSeconds": 0,
+ "RequiresPlainVideoItems": false,
+ "RequiresPlainFolders": false,
+ "EnableMSMediaReceiverRegistrar": false,
+ "IgnoreTranscodeByteRangeRequests": false,
+ "DirectPlayProfiles": [
+ {
+ "Container": "webm",
+ "AudioCodec": "vorbis,opus",
+ "VideoCodec": "vp8,vp9",
+ "Type": "Video",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "mp4,m4v",
+ "AudioCodec": "aac,mp3,ac3,eac3,mp2,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
+ "VideoCodec": "h264,hevc,mpeg2video,vc1,msmpeg4v2,vp8,vp9",
+ "Type": "Video",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "mkv",
+ "AudioCodec": "aac,mp3,ac3,eac3,mp2,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
+ "VideoCodec": "h264,hevc,mpeg2video,vc1,msmpeg4v2,vp8,vp9",
+ "Type": "Video",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "m2ts",
+ "AudioCodec": "aac,mp3,ac3,eac3,mp2,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
+ "VideoCodec": "h264,vc1,mpeg2video",
+ "Type": "Video",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "wmv",
+ "AudioCodec": "wma",
+ "VideoCodec": "wmv,vc1",
+ "Type": "Video",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "ts,mpegts",
+ "AudioCodec": "aac,mp3,ac3,eac3,mp2,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
+ "VideoCodec": "h264,hevc,vc1,mpeg2video",
+ "Type": "Video",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "asf",
+ "AudioCodec": "wma",
+ "VideoCodec": "wmv,vc1",
+ "Type": "Video",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "avi",
+ "AudioCodec": "aac,mp3,ac3,eac3,mp2,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
+ "VideoCodec": "h264,hevc",
+ "Type": "Video",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "mpg,mpeg,flv,3gp,mts,trp,vob,vro",
+ "AudioCodec": "aac,mp3,ac3,eac3,mp2,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
+ "VideoCodec": "",
+ "Type": "Video",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "mov",
+ "AudioCodec": "aac,mp3,ac3,eac3,mp2,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
+ "VideoCodec": "h264",
+ "Type": "Video",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "opus",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "webm",
+ "AudioCodec": "opus",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "mp3",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "aac",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "m4a",
+ "AudioCodec": "aac",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "m4b",
+ "AudioCodec": "aac",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "flac",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "webma",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "webm",
+ "AudioCodec": "webma",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "wma",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "wav",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ },
+ {
+ "Container": "ogg",
+ "Type": "Audio",
+ "$type": "DirectPlayProfile"
+ }
+ ],
+ "TranscodingProfiles": [
+ {
+ "Container": "aac",
+ "Type": "Audio",
+ "AudioCodec": "aac",
+ "Protocol": "hls",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Streaming",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "6",
+ "MinSegments": 1,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": true,
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "aac",
+ "Type": "Audio",
+ "AudioCodec": "aac",
+ "Protocol": "http",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Streaming",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "6",
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "mp3",
+ "Type": "Audio",
+ "AudioCodec": "mp3",
+ "Protocol": "http",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Streaming",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "6",
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "opus",
+ "Type": "Audio",
+ "AudioCodec": "opus",
+ "Protocol": "http",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Streaming",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "6",
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "wav",
+ "Type": "Audio",
+ "AudioCodec": "wav",
+ "Protocol": "http",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Streaming",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "6",
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "opus",
+ "Type": "Audio",
+ "AudioCodec": "opus",
+ "Protocol": "http",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Static",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "6",
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "mp3",
+ "Type": "Audio",
+ "AudioCodec": "mp3",
+ "Protocol": "http",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Static",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "6",
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "aac",
+ "Type": "Audio",
+ "AudioCodec": "aac",
+ "Protocol": "http",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Static",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "6",
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "wav",
+ "Type": "Audio",
+ "AudioCodec": "wav",
+ "Protocol": "http",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Static",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "6",
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "mkv",
+ "Type": "Video",
+ "VideoCodec": "h264,hevc,mpeg2video,vc1,msmpeg4v2,vp8,vp9",
+ "AudioCodec": "aac,mp3,ac3,eac3,mp2,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
+ "Protocol": "",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": true,
+ "Context": "Static",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "6",
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "Conditions": [
+ {
+ "Condition": "LessThanEqual",
+ "Property": "Width",
+ "Value": "3840",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ }
+ ],
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "ts",
+ "Type": "Video",
+ "VideoCodec": "h264,hevc",
+ "AudioCodec": "aac,mp3,ac3,eac3,opus",
+ "Protocol": "hls",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Streaming",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "6",
+ "MinSegments": 1,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "Conditions": [
+ {
+ "Condition": "LessThanEqual",
+ "Property": "Width",
+ "Value": "3840",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ }
+ ],
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "webm",
+ "Type": "Video",
+ "VideoCodec": "vp8,vp9,vpx",
+ "AudioCodec": "vorbis,opus",
+ "Protocol": "http",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Streaming",
+ "EnableSubtitlesInManifest": false,
+ "MaxAudioChannels": "6",
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "Conditions": [
+ {
+ "Condition": "LessThanEqual",
+ "Property": "Width",
+ "Value": "3840",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ }
+ ],
+ "$type": "TranscodingProfile"
+ },
+ {
+ "Container": "mp4",
+ "Type": "Video",
+ "VideoCodec": "h264",
+ "AudioCodec": "aac,mp3,ac3,eac3,mp2,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
+ "Protocol": "http",
+ "EstimateContentLength": false,
+ "EnableMpegtsM2TsMode": false,
+ "TranscodeSeekInfo": "Auto",
+ "CopyTimestamps": false,
+ "Context": "Static",
+ "EnableSubtitlesInManifest": false,
+ "MinSegments": 0,
+ "SegmentLength": 0,
+ "BreakOnNonKeyFrames": false,
+ "Conditions": [
+ {
+ "Condition": "LessThanEqual",
+ "Property": "Width",
+ "Value": "3840",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ }
+ ],
+ "$type": "TranscodingProfile"
+ }
+ ],
+ "CodecProfiles": [
+ {
+ "Type": "Video",
+ "Conditions": [
+ {
+ "Condition": "NotEquals",
+ "Property": "IsAnamorphic",
+ "Value": "true",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ },
+ {
+ "Condition": "EqualsAny",
+ "Property": "VideoProfile",
+ "Value": "high|main|baseline|constrained baseline|high 10",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ },
+ {
+ "Condition": "LessThanEqual",
+ "Property": "VideoLevel",
+ "Value": "52",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ }
+ ],
+ "Codec": "h264",
+ "$type": "CodecProfile"
+ },
+ {
+ "Type": "Video",
+ "Conditions": [
+ {
+ "Condition": "NotEquals",
+ "Property": "IsAnamorphic",
+ "Value": "true",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ },
+ {
+ "Condition": "EqualsAny",
+ "Property": "VideoProfile",
+ "Value": "main|main 10",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ },
+ {
+ "Condition": "LessThanEqual",
+ "Property": "VideoLevel",
+ "Value": "183",
+ "IsRequired": false,
+ "$type": "ProfileCondition"
+ }
+ ],
+ "Codec": "hevc",
+ "$type": "CodecProfile"
+ }
+ ],
+ "ResponseProfiles": [
+ {
+ "Container": "m4v",
+ "Type": "Video",
+ "MimeType": "video/mp4",
+ "$type": "ResponseProfile"
+ }
+ ],
+ "SubtitleProfiles": [
+ {
+ "Format": "vtt",
+ "Method": "External",
+ "$type": "SubtitleProfile"
+ },
+ {
+ "Format": "ass",
+ "Method": "External",
+ "$type": "SubtitleProfile"
+ },
+ {
+ "Format": "ssa",
+ "Method": "External",
+ "$type": "SubtitleProfile"
+ }
+ ],
+ "$type": "DeviceProfile"
+}
diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-dts-srt-2600k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-dts-srt-2600k.json
new file mode 100644
index 000000000..224365df6
--- /dev/null
+++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-dts-srt-2600k.json
@@ -0,0 +1,70 @@
+{
+ "Id": "a766d122b58e45d9492d17af77748bf5",
+ "Path": "/Media/MyVideo-720p.mp4",
+ "Container": "mov,mp4,m4a,3gp,3g2,mj2",
+ "Size": 835317696,
+ "Name": "MyVideo-720p",
+ "ETag": "579a34c6d5dfb21d81539a51220b6a23",
+ "RunTimeTicks": 25801230336,
+ "SupportsTranscoding": true,
+ "SupportsDirectStream": true,
+ "SupportsDirectPlay": true,
+ "SupportsProbing": true,
+ "MediaStreams": [
+ {
+ "Codec": "h264",
+ "CodecTag": "avc1",
+ "Language": "eng",
+ "TimeBase": "1/11988",
+ "VideoRange": "SDR",
+ "DisplayTitle": "720p H264 SDR",
+ "NalLengthSize": "0",
+ "BitRate": 2032876,
+ "BitDepth": 8,
+ "RefFrames": 1,
+ "IsDefault": true,
+ "Height": 720,
+ "Width": 1280,
+ "AverageFrameRate": 23.976,
+ "RealFrameRate": 23.976,
+ "Profile": "High",
+ "Type": 1,
+ "AspectRatio": "16:9",
+ "PixelFormat": "yuv420p",
+ "Level": 41
+ },
+ {
+ "Codec": "dts",
+ "Language": "eng",
+ "TimeBase": "1/48000",
+ "DisplayTitle": "En - DTS - 5.1 - Default",
+ "ChannelLayout": "5.1",
+ "BitRate": 384000,
+ "Channels": 6,
+ "SampleRate": 48000,
+ "IsDefault": true,
+ "Index": 1,
+ "Score": 202
+ },
+ {
+ "Codec": "srt",
+ "Language": "eng",
+ "TimeBase": "1/1000000",
+ "localizedUndefined": "Undefined",
+ "localizedDefault": "Default",
+ "localizedForced": "Forced",
+ "DisplayTitle": "En - Default",
+ "BitRate": 92,
+ "IsDefault": true,
+ "Type": 2,
+ "Index": 2,
+ "Score": 6421,
+ "IsExternal": true,
+ "IsTextSubtitleStream": true,
+ "SupportsExternalStream": true
+ }
+ ],
+ "Bitrate": 2590008,
+ "DefaultAudioStreamIndex": 1,
+ "DefaultSubtitleStreamIndex": 2
+}
diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-truehd-srt-15200k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-truehd-srt-15200k.json
new file mode 100644
index 000000000..a5393966f
--- /dev/null
+++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-truehd-srt-15200k.json
@@ -0,0 +1,74 @@
+{
+ "Id": "f6eab7118618ab26e61e495a1853481a",
+ "Path": "/Media/MyVideo-WEBDL-2160p.mp4",
+ "Container": "mov,mp4,m4a,3gp,3g2,mj2",
+ "Size": 6521110016,
+ "Name": "MyVideo WEBDL-2160p",
+ "ETag": "a2fb84b618ba2467fe377543f879e9bf",
+ "RunTimeTicks": 34318510080,
+ "SupportsTranscoding": true,
+ "SupportsDirectStream": true,
+ "SupportsDirectPlay": true,
+ "SupportsProbing": true,
+ "MediaStreams": [
+ {
+ "Codec": "hevc",
+ "CodecTag": "hev1",
+ "Language": "eng",
+ "ColorSpace": "bt2020nc",
+ "ColorTransfer": "smpte2084",
+ "ColorPrimaries": "bt2020",
+ "TimeBase": "1/16000",
+ "VideoRange": "HDR",
+ "DisplayTitle": "4K HEVC HDR",
+ "BitRate": 14715079,
+ "BitDepth": 8,
+ "RefFrames": 1,
+ "IsDefault": true,
+ "Height": 2160,
+ "Width": 3840,
+ "AverageFrameRate": 23.976,
+ "RealFrameRate": 23.976,
+ "Profile": "Main 10",
+ "Type": 1,
+ "AspectRatio": "16:9",
+ "PixelFormat": "yuv420p10le",
+ "Level": 150
+ },
+ {
+ "Codec": "truehd",
+ "CodecTag": "AC-3",
+ "Language": "eng",
+ "TimeBase": "1/48000",
+ "DisplayTitle": "TRUEHD - 7.1",
+ "ChannelLayout": "7.1",
+ "BitRate": 384000,
+ "Channels": 8,
+ "SampleRate": 48000,
+ "IsDefault": true,
+ "Index": 1,
+ "Score": 203
+ },
+ {
+ "Codec": "srt",
+ "Language": "eng",
+ "TimeBase": "1/1000000",
+ "localizedUndefined": "Undefined",
+ "localizedDefault": "Default",
+ "localizedForced": "Forced",
+ "DisplayTitle": "En - Default",
+ "BitRate": 92,
+ "IsDefault": true,
+ "Type": 2,
+ "Index": 2,
+ "Score": 6421,
+ "IsExternal": true,
+ "IsTextSubtitleStream": true,
+ "SupportsExternalStream": true,
+ "Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt"
+ }
+ ],
+ "Bitrate": 15201382,
+ "DefaultAudioStreamIndex": 1,
+ "DefaultSubtitleStreamIndex": 2
+}
diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-no-streams.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-no-streams.json
new file mode 100644
index 000000000..86713e255
--- /dev/null
+++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-no-streams.json
@@ -0,0 +1,17 @@
+{
+ "Id": "f6eab7118618ab26e61e495a1853481a",
+ "Path": "/Media/MyVideo-WEBDL-2160p.mp4",
+ "Container": "mov,mp4,m4a,3gp,3g2,mj2",
+ "Size": 6521110016,
+ "Name": "MyVideo WEBDL-2160p",
+ "ETag": "a2fb84b618ba2467fe377543f879e9bf",
+ "RunTimeTicks": 34318510080,
+ "SupportsTranscoding": true,
+ "SupportsDirectStream": true,
+ "SupportsDirectPlay": true,
+ "SupportsProbing": true,
+ "MediaStreams": [],
+ "Bitrate": 15201382,
+ "DefaultAudioStreamIndex": null,
+ "DefaultSubtitleStreamIndex": null
+}
diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
index 69ba5d86a..da0e9f5b1 100644
--- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
+++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
@@ -12,10 +12,13 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
- <PackageReference Include="Moq" Version="4.17.2" />
- <PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
+ <PackageReference Include="Moq" Version="4.18.2" />
+ <PackageReference Include="xunit" Version="2.4.2" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2" />
</ItemGroup>
@@ -30,7 +33,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs
index 1e7fedb36..68059f980 100644
--- a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs
@@ -1,4 +1,4 @@
-using Emby.Naming.Common;
+using Emby.Naming.Common;
using Emby.Naming.TV;
using Xunit;
@@ -9,6 +9,7 @@ namespace Jellyfin.Naming.Tests.TV
private readonly NamingOptions _namingOptions = new NamingOptions();
[Theory]
+ [InlineData("Season 21/One Piece 1001", 1001)]
[InlineData("Watchmen (2019)/Watchmen 1x03 [WEBDL-720p][EAC3 5.1][h264][-TBS] - She Was Killed by Space Junk.mkv", 3)]
[InlineData("The Daily Show/The Daily Show 25x22 - [WEBDL-720p][AAC 2.0][x264] Noah Baumbach-TBS.mkv", 22)]
[InlineData("Castle Rock 2x01 Que el rio siga su curso [WEB-DL HULU 1080p h264 Dual DD5.1 Subs].mkv", 1)]
diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
index 9a9a57be4..79f2366b8 100644
--- a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
@@ -2,7 +2,6 @@ using System.Collections.Generic;
using System.Linq;
using Emby.Naming.Common;
using Emby.Naming.Video;
-using MediaBrowser.Model.IO;
using Xunit;
namespace Jellyfin.Naming.Tests.Video
diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs
index b76187842..cc9cfdd7d 100644
--- a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs
@@ -3,7 +3,6 @@ using System.Linq;
using Emby.Naming.Common;
using Emby.Naming.Video;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
using Xunit;
namespace Jellyfin.Naming.Tests.Video
diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
index ba39cc0ff..8ec0262bd 100644
--- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
+++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
@@ -12,12 +12,15 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
- <PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
+ <PackageReference Include="xunit" Version="2.4.2" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2" />
- <PackageReference Include="FsCheck.Xunit" Version="2.16.4" />
- <PackageReference Include="Moq" Version="4.17.2" />
+ <PackageReference Include="FsCheck.Xunit" Version="2.16.5" />
+ <PackageReference Include="Moq" Version="4.18.2" />
</ItemGroup>
<!-- Code Analyzers-->
@@ -27,7 +30,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
index 6b9397437..166bc0513 100644
--- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
+++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
@@ -103,10 +103,7 @@ namespace Jellyfin.Networking.Tests
"[192.158.0.0/16,192.0.0.0/8]")]
public void TestCollections(string settings, string result1, string result2, string result3, string result4, string result5)
{
- if (settings == null)
- {
- throw new ArgumentNullException(nameof(settings));
- }
+ ArgumentNullException.ThrowIfNull(settings);
var conf = new NetworkConfiguration()
{
@@ -155,20 +152,11 @@ namespace Jellyfin.Networking.Tests
[InlineData("127.0.0.1", "127.0.0.1/8", "[127.0.0.1/32]")]
public void UnionCheck(string settings, string compare, string result)
{
- if (settings == null)
- {
- throw new ArgumentNullException(nameof(settings));
- }
+ ArgumentNullException.ThrowIfNull(settings);
- if (compare == null)
- {
- throw new ArgumentNullException(nameof(compare));
- }
+ ArgumentNullException.ThrowIfNull(compare);
- if (result == null)
- {
- throw new ArgumentNullException(nameof(result));
- }
+ ArgumentNullException.ThrowIfNull(result);
var conf = new NetworkConfiguration()
{
@@ -264,20 +252,11 @@ namespace Jellyfin.Networking.Tests
public void TestCollectionEquality(string source, string dest, string result)
{
- if (source == null)
- {
- throw new ArgumentNullException(nameof(source));
- }
+ ArgumentNullException.ThrowIfNull(source);
- if (dest == null)
- {
- throw new ArgumentNullException(nameof(dest));
- }
+ ArgumentNullException.ThrowIfNull(dest);
- if (result == null)
- {
- throw new ArgumentNullException(nameof(result));
- }
+ ArgumentNullException.ThrowIfNull(result);
var conf = new NetworkConfiguration()
{
@@ -331,20 +310,11 @@ namespace Jellyfin.Networking.Tests
[InlineData("", "", false, "eth16")]
public void TestBindInterfaces(string source, string bindAddresses, bool ipv6enabled, string result)
{
- if (source == null)
- {
- throw new ArgumentNullException(nameof(source));
- }
+ ArgumentNullException.ThrowIfNull(source);
- if (bindAddresses == null)
- {
- throw new ArgumentNullException(nameof(bindAddresses));
- }
+ ArgumentNullException.ThrowIfNull(bindAddresses);
- if (result == null)
- {
- throw new ArgumentNullException(nameof(result));
- }
+ ArgumentNullException.ThrowIfNull(result);
var conf = new NetworkConfiguration()
{
@@ -393,7 +363,7 @@ namespace Jellyfin.Networking.Tests
// User on external network, internal binding only - so assumption is a proxy forward, return external override.
[InlineData("jellyfin.org", "192.168.1.0/24", "eth16", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")]
- // User on external network, no binding - so result is the 1st external which is overriden.
+ // User on external network, no binding - so result is the 1st external which is overridden.
[InlineData("jellyfin.org", "192.168.1.0/24", "", false, "0.0.0.0 = http://helloworld.com", "http://helloworld.com")]
// User assumed to be internal, no binding - so result is the 1st internal.
@@ -403,15 +373,9 @@ namespace Jellyfin.Networking.Tests
[InlineData("192.168.1.1", "192.168.1.0/24", "", false, "eth16=http://helloworld.com", "http://helloworld.com")]
public void TestBindInterfaceOverrides(string source, string lan, string bindAddresses, bool ipv6enabled, string publishedServers, string result)
{
- if (lan == null)
- {
- throw new ArgumentNullException(nameof(lan));
- }
+ ArgumentNullException.ThrowIfNull(lan);
- if (bindAddresses == null)
- {
- throw new ArgumentNullException(nameof(bindAddresses));
- }
+ ArgumentNullException.ThrowIfNull(bindAddresses);
var conf = new NetworkConfiguration()
{
diff --git a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
index 6d52a3cd6..194229737 100644
--- a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
+++ b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
@@ -13,10 +13,10 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
- <PackageReference Include="Moq" Version="4.17.2" />
- <PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
+ <PackageReference Include="Moq" Version="4.18.2" />
+ <PackageReference Include="xunit" Version="2.4.2" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
@@ -33,7 +33,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs
index c0931dbcf..08b343cd8 100644
--- a/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs
+++ b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs
@@ -44,7 +44,7 @@ namespace Jellyfin.Providers.Tests.Manager
ValidateImages_Test(ImageType.Primary, 0, true, 0, false, 0);
}
- private static TheoryData<ImageType, int> GetImageTypesWithCount()
+ public static TheoryData<ImageType, int> GetImageTypesWithCount()
{
var theoryTypes = new TheoryData<ImageType, int>
{
diff --git a/tests/Jellyfin.Providers.Tests/Manager/MetadataServiceTests.cs b/tests/Jellyfin.Providers.Tests/Manager/MetadataServiceTests.cs
index b74b331b7..28b2e1d8f 100644
--- a/tests/Jellyfin.Providers.Tests/Manager/MetadataServiceTests.cs
+++ b/tests/Jellyfin.Providers.Tests/Manager/MetadataServiceTests.cs
@@ -132,7 +132,7 @@ namespace Jellyfin.Providers.Tests.Manager
Assert.True(TestMergeBaseItemData<Audio, SongInfo>(propName, oldValue, Array.Empty<string>(), null, true, out _));
}
- private static TheoryData<string, object, object> MergeBaseItemData_SimpleField_ReplacesAppropriately_TestData()
+ public static TheoryData<string, object, object> MergeBaseItemData_SimpleField_ReplacesAppropriately_TestData()
=> new()
{
{ "IndexNumber", 1, 2 },
diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/AudioResolverTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/AudioResolverTests.cs
index aec523882..33a9aca31 100644
--- a/tests/Jellyfin.Providers.Tests/MediaInfo/AudioResolverTests.cs
+++ b/tests/Jellyfin.Providers.Tests/MediaInfo/AudioResolverTests.cs
@@ -13,6 +13,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Providers.MediaInfo;
+using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
@@ -43,6 +44,9 @@ public class AudioResolverTests
MediaStreams = new List<MediaStream>
{
new()
+ {
+ Type = MediaStreamType.Audio
+ }
}
}));
@@ -52,7 +56,7 @@ public class AudioResolverTests
fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MediaInfoResolverTests.MetadataDirectoryRegex)))
.Returns(true);
- _audioResolver = new AudioResolver(localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
+ _audioResolver = new AudioResolver(Mock.Of<ILogger<AudioResolver>>(), localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
}
[Theory]
diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/MediaInfoResolverTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/MediaInfoResolverTests.cs
index 98b4a6ccf..57674bb7f 100644
--- a/tests/Jellyfin.Providers.Tests/MediaInfo/MediaInfoResolverTests.cs
+++ b/tests/Jellyfin.Providers.Tests/MediaInfo/MediaInfoResolverTests.cs
@@ -18,6 +18,7 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Providers.MediaInfo;
+using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
@@ -70,7 +71,7 @@ public class MediaInfoResolverTests
fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MetadataDirectoryRegex)))
.Returns(true);
- _subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
+ _subtitleResolver = new SubtitleResolver(Mock.Of<ILogger<SubtitleResolver>>(), _localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
}
[Fact]
@@ -201,14 +202,14 @@ public class MediaInfoResolverTests
var mediaEncoder = Mock.Of<IMediaEncoder>(MockBehavior.Strict);
var fileSystem = Mock.Of<IFileSystem>();
- var subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder, fileSystem, new NamingOptions());
+ var subtitleResolver = new SubtitleResolver(Mock.Of<ILogger<SubtitleResolver>>(), _localizationManager, mediaEncoder, fileSystem, new NamingOptions());
var streams = await subtitleResolver.GetExternalStreamsAsync(video, 0, directoryService.Object, false, CancellationToken.None);
Assert.Empty(streams);
}
- private static TheoryData<string, MediaStream[], MediaStream[]> GetExternalStreams_MergeMetadata_HandlesOverridesCorrectly_Data()
+ public static TheoryData<string, MediaStream[], MediaStream[]> GetExternalStreams_MergeMetadata_HandlesOverridesCorrectly_Data()
{
var data = new TheoryData<string, MediaStream[], MediaStream[]>();
@@ -306,7 +307,7 @@ public class MediaInfoResolverTests
fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MetadataDirectoryRegex)))
.Returns(true);
- var subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
+ var subtitleResolver = new SubtitleResolver(Mock.Of<ILogger<SubtitleResolver>>(), _localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
var directoryService = GetDirectoryServiceForExternalFile(file);
var streams = await subtitleResolver.GetExternalStreamsAsync(video, 0, directoryService, false, CancellationToken.None);
@@ -359,7 +360,10 @@ public class MediaInfoResolverTests
var mediaStreams = new List<MediaStream>();
for (int i = 0; i < streamCount; i++)
{
- mediaStreams.Add(new());
+ mediaStreams.Add(new()
+ {
+ Type = MediaStreamType.Subtitle
+ });
}
return mediaStreams;
@@ -378,7 +382,7 @@ public class MediaInfoResolverTests
fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MetadataDirectoryRegex)))
.Returns(true);
- var subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
+ var subtitleResolver = new SubtitleResolver(Mock.Of<ILogger<SubtitleResolver>>(), _localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
int startIndex = 1;
var streams = await subtitleResolver.GetExternalStreamsAsync(video, startIndex, directoryService.Object, false, CancellationToken.None);
diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs
index 0e6457ce3..0c1c269a4 100644
--- a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs
+++ b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs
@@ -13,6 +13,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Providers.MediaInfo;
+using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
@@ -43,6 +44,9 @@ public class SubtitleResolverTests
MediaStreams = new List<MediaStream>
{
new()
+ {
+ Type = MediaStreamType.Subtitle
+ }
}
}));
@@ -52,7 +56,7 @@ public class SubtitleResolverTests
fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MediaInfoResolverTests.MetadataDirectoryRegex)))
.Returns(true);
- _subtitleResolver = new SubtitleResolver(localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
+ _subtitleResolver = new SubtitleResolver(Mock.Of<ILogger<SubtitleResolver>>(), localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
}
[Theory]
diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs
index 7e88cdb20..6b2a05241 100644
--- a/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs
+++ b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs
@@ -19,7 +19,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo
{
public class VideoImageProviderTests
{
- private static TheoryData<Video> GetImage_UnsupportedInput_ReturnsNoImage_TestData()
+ public static TheoryData<Video> GetImage_UnsupportedInput_ReturnsNoImage_TestData()
{
return new()
{
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 2d0a7c031..918802d77 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -21,10 +21,13 @@
<ItemGroup>
<PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
- <PackageReference Include="Moq" Version="4.17.2" />
- <PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
+ <PackageReference Include="Moq" Version="4.18.2" />
+ <PackageReference Include="xunit" Version="2.4.2" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
<PackageReference Include="Xunit.SkippableFact" Version="1.4.13" />
<PackageReference Include="coverlet.collector" Version="3.1.2" />
</ItemGroup>
@@ -36,7 +39,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/MediaStreamSelectorTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/MediaStreamSelectorTests.cs
index d59f2f4e5..538010f6c 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/MediaStreamSelectorTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/MediaStreamSelectorTests.cs
@@ -16,15 +16,39 @@ public class MediaStreamSelectorTests
}
[Theory]
- [InlineData(true)]
- [InlineData(false)]
- public void GetDefaultAudioStreamIndex_WithoutDefault_NotNull(bool preferDefaultTrack)
+ [InlineData(new string[0], false, 1)]
+ [InlineData(new string[0], true, 1)]
+ [InlineData(new[] { "eng" }, false, 2)]
+ [InlineData(new[] { "eng" }, true, 1)]
+ [InlineData(new[] { "eng", "fre" }, false, 2)]
+ [InlineData(new[] { "fre", "eng" }, false, 1)]
+ [InlineData(new[] { "eng", "fre" }, true, 1)]
+ public void GetDefaultAudioStreamIndex_PreferredLanguage_SelectsCorrect(string[] preferredLanguages, bool preferDefaultTrack, int expectedIndex)
{
- var streams = new[]
+ var streams = new MediaStream[]
{
- new MediaStream()
+ new()
+ {
+ Index = 0,
+ Type = MediaStreamType.Video,
+ IsDefault = true
+ },
+ new()
+ {
+ Index = 1,
+ Type = MediaStreamType.Audio,
+ Language = "fre",
+ IsDefault = true
+ },
+ new()
+ {
+ Index = 2,
+ Type = MediaStreamType.Audio,
+ Language = "eng",
+ IsDefault = false
+ }
};
- Assert.NotNull(MediaStreamSelector.GetDefaultAudioStreamIndex(streams, Array.Empty<string>(), preferDefaultTrack));
+ Assert.Equal(expectedIndex, MediaStreamSelector.GetDefaultAudioStreamIndex(streams, preferredLanguages, preferDefaultTrack));
}
}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs
index 09aec82b0..f107b1ef9 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs
@@ -85,6 +85,17 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv
EpisodeTitle = "The VCR Illumination"
});
+ data.Add(
+ "Lorem ipsum dolor sit amet: consect 2018_12_06_21_06_00",
+ new TimerInfo
+ {
+ Name = "Lorem ipsum dolor sit amet: consect",
+ IsProgramSeries = true,
+ StartDate = new DateTime(2018, 12, 6, 21, 6, 0, DateTimeKind.Local),
+ OriginalAirDate = new DateTime(2018, 12, 6),
+ EpisodeTitle = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor"
+ });
+
return data;
}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs
index 3b3e38bd1..e1d2bb2d5 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs
@@ -18,7 +18,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect
}
/// <summary>
- /// /token reponse.
+ /// /token response.
/// </summary>
[Fact]
public void Deserialize_Token_Response_Live_Success()
diff --git a/tests/Jellyfin.Server.Implementations.Tests/QuickConnect/QuickConnectManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/QuickConnect/QuickConnectManagerTests.cs
index 28d832ef8..c32d89ea5 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/QuickConnect/QuickConnectManagerTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/QuickConnect/QuickConnectManagerTests.cs
@@ -50,7 +50,10 @@ namespace Jellyfin.Server.Implementations.Tests.QuickConnect
[Fact]
public void IsEnabled_QuickConnectUnavailable_False()
- => Assert.False(_quickConnectManager.IsEnabled);
+ {
+ _config.QuickConnectAvailable = false;
+ Assert.False(_quickConnectManager.IsEnabled);
+ }
[Theory]
[InlineData("", "DeviceId", "Client", "1.0.0")]
@@ -69,19 +72,31 @@ namespace Jellyfin.Server.Implementations.Tests.QuickConnect
[Fact]
public void TryConnect_QuickConnectUnavailable_ThrowsAuthenticationException()
- => Assert.Throws<AuthenticationException>(() => _quickConnectManager.TryConnect(_quickConnectAuthInfo));
+ {
+ _config.QuickConnectAvailable = false;
+ Assert.Throws<AuthenticationException>(() => _quickConnectManager.TryConnect(_quickConnectAuthInfo));
+ }
[Fact]
public void CheckRequestStatus_QuickConnectUnavailable_ThrowsAuthenticationException()
- => Assert.Throws<AuthenticationException>(() => _quickConnectManager.CheckRequestStatus(string.Empty));
+ {
+ _config.QuickConnectAvailable = false;
+ Assert.Throws<AuthenticationException>(() => _quickConnectManager.CheckRequestStatus(string.Empty));
+ }
[Fact]
public void AuthorizeRequest_QuickConnectUnavailable_ThrowsAuthenticationException()
- => Assert.ThrowsAsync<AuthenticationException>(() => _quickConnectManager.AuthorizeRequest(Guid.Empty, string.Empty));
+ {
+ _config.QuickConnectAvailable = false;
+ Assert.ThrowsAsync<AuthenticationException>(() => _quickConnectManager.AuthorizeRequest(Guid.Empty, string.Empty));
+ }
[Fact]
public void GetAuthorizedRequest_QuickConnectUnavailable_ThrowsAuthenticationException()
- => Assert.Throws<AuthenticationException>(() => _quickConnectManager.GetAuthorizedRequest(string.Empty));
+ {
+ _config.QuickConnectAvailable = false;
+ Assert.Throws<AuthenticationException>(() => _quickConnectManager.GetAuthorizedRequest(string.Empty));
+ }
[Fact]
public void IsEnabled_QuickConnectAvailable_True()
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Sorting/IndexNumberComparerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Sorting/IndexNumberComparerTests.cs
new file mode 100644
index 000000000..18588bd67
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Sorting/IndexNumberComparerTests.cs
@@ -0,0 +1,49 @@
+using System;
+using Emby.Server.Implementations.Sorting;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Sorting;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Sorting;
+
+public class IndexNumberComparerTests
+{
+ private readonly IBaseItemComparer _cmp = new IndexNumberComparer();
+
+ public static TheoryData<BaseItem?, BaseItem?> Compare_GivenNull_ThrowsArgumentNullException_TestData()
+ => new()
+ {
+ { null, new Audio() },
+ { new Audio(), null }
+ };
+
+ [Theory]
+ [MemberData(nameof(Compare_GivenNull_ThrowsArgumentNullException_TestData))]
+ public void Compare_GivenNull_ThrowsArgumentNullException(BaseItem? x, BaseItem? y)
+ {
+ Assert.Throws<ArgumentNullException>(() => _cmp.Compare(x, y));
+ }
+
+ [Theory]
+ [InlineData(null, null, 0)]
+ [InlineData(0, null, 1)]
+ [InlineData(null, 0, -1)]
+ [InlineData(1, 1, 0)]
+ [InlineData(0, 1, -1)]
+ [InlineData(1, 0, 1)]
+ public void Compare_ValidIndices_SortsExpected(int? index1, int? index2, int expected)
+ {
+ BaseItem x = new Audio
+ {
+ IndexNumber = index1
+ };
+ BaseItem y = new Audio
+ {
+ IndexNumber = index2
+ };
+
+ Assert.Equal(expected, _cmp.Compare(x, y));
+ Assert.Equal(-expected, _cmp.Compare(y, x));
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Sorting/ParentIndexNumberComparerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Sorting/ParentIndexNumberComparerTests.cs
new file mode 100644
index 000000000..261092e01
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Sorting/ParentIndexNumberComparerTests.cs
@@ -0,0 +1,49 @@
+using System;
+using Emby.Server.Implementations.Sorting;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Sorting;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Sorting;
+
+public class ParentIndexNumberComparerTests
+{
+ private readonly IBaseItemComparer _cmp = new ParentIndexNumberComparer();
+
+ public static TheoryData<BaseItem?, BaseItem?> Compare_GivenNull_ThrowsArgumentNullException_TestData()
+ => new()
+ {
+ { null, new Audio() },
+ { new Audio(), null }
+ };
+
+ [Theory]
+ [MemberData(nameof(Compare_GivenNull_ThrowsArgumentNullException_TestData))]
+ public void Compare_GivenNull_ThrowsArgumentNullException(BaseItem? x, BaseItem? y)
+ {
+ Assert.Throws<ArgumentNullException>(() => _cmp.Compare(x, y));
+ }
+
+ [Theory]
+ [InlineData(null, null, 0)]
+ [InlineData(0, null, 1)]
+ [InlineData(null, 0, -1)]
+ [InlineData(1, 1, 0)]
+ [InlineData(0, 1, -1)]
+ [InlineData(1, 0, 1)]
+ public void Compare_ValidIndices_SortsExpected(int? parentIndex1, int? parentIndex2, int expected)
+ {
+ BaseItem x = new Audio
+ {
+ ParentIndexNumber = parentIndex1
+ };
+ BaseItem y = new Audio
+ {
+ ParentIndexNumber = parentIndex2
+ };
+
+ Assert.Equal(expected, _cmp.Compare(x, y));
+ Assert.Equal(-expected, _cmp.Compare(y, x));
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/manifest-stable.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/manifest-stable.json
index b766e668e..fa8fbd8d2 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/manifest-stable.json
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/manifest-stable.json
@@ -253,7 +253,7 @@
"versions": [
{
"version": "5.0.0.0",
- "changelog": "Updated to use NextPVR API v5, no longer compatable with API v4.\n",
+ "changelog": "Updated to use NextPVR API v5, no longer compatible with API v4.\n",
"targetAbi": "10.7.0.0",
"sourceUrl": "https://repo.jellyfin.org/releases/plugin/nextpvr/nextpvr_5.0.0.0.zip",
"checksum": "d70f694d14bf9462ba2b2ebe110068d3",
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
index 0afb6f88d..52df1cd60 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
@@ -83,6 +83,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
var res = await response.Content.ReadAsStreamAsync();
var data = await JsonSerializer.DeserializeAsync<ConfigurationPageInfo[]>(res, _jsonOpions);
+ Assert.NotNull(data);
Assert.Empty(data);
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs
index 5d7b0e874..a65f65bb2 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs
@@ -34,8 +34,8 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
- using var getResponse = await client.GetAsync("/Dlna/Profiles/" + NonExistentProfile).ConfigureAwait(false);
- Assert.Equal(HttpStatusCode.NotFound, getResponse.StatusCode);
+ using var response = await client.GetAsync("/Dlna/Profiles/" + NonExistentProfile).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
@@ -45,8 +45,8 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
- using var getResponse = await client.DeleteAsync("/Dlna/Profiles/" + NonExistentProfile).ConfigureAwait(false);
- Assert.Equal(HttpStatusCode.NotFound, getResponse.StatusCode);
+ using var response = await client.DeleteAsync("/Dlna/Profiles/" + NonExistentProfile).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
@@ -61,8 +61,8 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Name = "ThisProfileDoesNotExist"
};
- using var getResponse = await client.PostAsJsonAsync("/Dlna/Profiles/" + NonExistentProfile, deviceProfile, _jsonOptions).ConfigureAwait(false);
- Assert.Equal(HttpStatusCode.NotFound, getResponse.StatusCode);
+ using var response = await client.PostAsJsonAsync("/Dlna/Profiles/" + NonExistentProfile, deviceProfile, _jsonOptions).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
@@ -77,8 +77,8 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Name = "ThisProfileIsNew"
};
- using var getResponse = await client.PostAsJsonAsync("/Dlna/Profiles", deviceProfile, _jsonOptions).ConfigureAwait(false);
- Assert.Equal(HttpStatusCode.NoContent, getResponse.StatusCode);
+ using var response = await client.PostAsJsonAsync("/Dlna/Profiles", deviceProfile, _jsonOptions).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
}
[Fact]
@@ -115,20 +115,46 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Id = _newDeviceProfileId
};
- using var getResponse = await client.PostAsJsonAsync("/Dlna/Profiles", updatedProfile, _jsonOptions).ConfigureAwait(false);
- Assert.Equal(HttpStatusCode.NoContent, getResponse.StatusCode);
+ using var postResponse = await client.PostAsJsonAsync("/Dlna/Profiles/" + _newDeviceProfileId, updatedProfile, _jsonOptions).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode);
+
+ // Verify that the profile got updated
+ using var response = await client.GetAsync("/Dlna/ProfileInfos").ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
+ Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
+
+ var profiles = await JsonSerializer.DeserializeAsync<DeviceProfileInfo[]>(
+ await response.Content.ReadAsStreamAsync().ConfigureAwait(false),
+ _jsonOptions).ConfigureAwait(false);
+
+ Assert.Null(profiles?.FirstOrDefault(x => string.Equals(x.Name, "ThisProfileIsNew", StringComparison.Ordinal)));
+ var newProfile = profiles?.FirstOrDefault(x => string.Equals(x.Name, "ThisProfileIsUpdated", StringComparison.Ordinal));
+ Assert.NotNull(newProfile);
+ _newDeviceProfileId = newProfile!.Id;
}
[Fact]
- [Priority(4)]
+ [Priority(5)]
public async Task DeleteProfile_Valid_NoContent()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
- using var getResponse = await client.DeleteAsync("/Dlna/Profiles/" + _newDeviceProfileId).ConfigureAwait(false);
- Console.WriteLine(await getResponse.Content.ReadAsStringAsync().ConfigureAwait(false));
- Assert.Equal(HttpStatusCode.NoContent, getResponse.StatusCode);
+ using var deleteResponse = await client.DeleteAsync("/Dlna/Profiles/" + _newDeviceProfileId).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NoContent, deleteResponse.StatusCode);
+
+ // Verify that the profile got deleted
+ using var response = await client.GetAsync("/Dlna/ProfileInfos").ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
+ Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
+
+ var profiles = await JsonSerializer.DeserializeAsync<DeviceProfileInfo[]>(
+ await response.Content.ReadAsStreamAsync().ConfigureAwait(false),
+ _jsonOptions).ConfigureAwait(false);
+
+ Assert.Null(profiles?.FirstOrDefault(x => string.Equals(x.Name, "ThisProfileIsUpdated", StringComparison.Ordinal)));
}
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
index e72dacfe0..0dd22644a 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
@@ -62,7 +62,9 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
using var contentStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
var user = await JsonSerializer.DeserializeAsync<StartupUserDto>(contentStream, _jsonOptions).ConfigureAwait(false);
- Assert.NotEmpty(user!.Name);
+ Assert.NotNull(user);
+ Assert.NotNull(user.Name);
+ Assert.NotEmpty(user.Name);
Assert.Null(user.Password);
}
@@ -87,7 +89,9 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
var contentStream = await getResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
var newUser = await JsonSerializer.DeserializeAsync<StartupUserDto>(contentStream, _jsonOptions).ConfigureAwait(false);
- Assert.Equal(user.Name, newUser!.Name);
+ Assert.NotNull(newUser);
+ Assert.Equal(user.Name, newUser.Name);
+ Assert.NotNull(newUser.Password);
Assert.NotEmpty(newUser.Password);
Assert.NotEqual(user.Password, newUser.Password);
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
index 9d34c39a2..2b825a93a 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
@@ -46,6 +46,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
var users = await JsonSerializer.DeserializeAsync<UserDto[]>(
await response.Content.ReadAsStreamAsync().ConfigureAwait(false), _jsonOpions).ConfigureAwait(false);
// User are hidden by default
+ Assert.NotNull(users);
Assert.Empty(users);
}
@@ -60,6 +61,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var users = await JsonSerializer.DeserializeAsync<UserDto[]>(
await response.Content.ReadAsStreamAsync().ConfigureAwait(false), _jsonOpions).ConfigureAwait(false);
+ Assert.NotNull(users);
Assert.Single(users);
Assert.False(users![0].HasConfiguredPassword);
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
index 8e0a208af..659737392 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
+++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
@@ -9,14 +9,17 @@
<PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.3" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.9" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
- <PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
+ <PackageReference Include="xunit" Version="2.4.2" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
<PackageReference Include="Xunit.Priority" Version="1.1.6" />
<PackageReference Include="coverlet.collector" Version="3.1.2" />
- <PackageReference Include="Moq" Version="4.17.2" />
+ <PackageReference Include="Moq" Version="4.18.2" />
</ItemGroup>
<ItemGroup>
@@ -33,7 +36,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs
index adaf624a9..48c49bf84 100644
--- a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs
@@ -11,7 +11,6 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Extensions.Logging;
-using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
namespace Jellyfin.Server.Integration.Tests
{
diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
index 90bd74bd9..b2653b28b 100644
--- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
+++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
@@ -10,13 +10,16 @@
<PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.3" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.9" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
- <PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
+ <PackageReference Include="xunit" Version="2.4.2" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2" />
- <PackageReference Include="Moq" Version="4.17.2" />
+ <PackageReference Include="Moq" Version="4.18.2" />
</ItemGroup>
<!-- Code Analyzers -->
@@ -26,7 +29,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
index d8bc4d596..f03448eed 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
@@ -13,10 +13,13 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
- <PackageReference Include="Moq" Version="4.17.2" />
- <PackageReference Include="xunit" Version="2.4.1" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
+ <PackageReference Include="Moq" Version="4.18.2" />
+ <PackageReference Include="xunit" Version="2.4.2" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2" />
</ItemGroup>
@@ -27,7 +30,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
index 7c9952030..988abce81 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Jellyfin.Data.Entities;