aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke <luke.pulverenti@gmail.com>2015-01-04 09:27:54 -0500
committerLuke <luke.pulverenti@gmail.com>2015-01-04 09:27:54 -0500
commitc5ff30f66e368efc2ca7dea7813fba6d9f6a657c (patch)
treec5552b898f66b7d510e9257eb8bbeafd6a003676
parent767590125b27c2498e3ad9544edbede30fb70f45 (diff)
parent59b6bc28c332701d5e383fbf99170bdc740fb6cc (diff)
Merge pull request #965 from MediaBrowser/dev
3.0.5482.0
-rw-r--r--MediaBrowser.Api/ApiEntryPoint.cs26
-rw-r--r--MediaBrowser.Api/ConfigurationService.cs2
-rw-r--r--MediaBrowser.Api/Dlna/DlnaServerService.cs8
-rw-r--r--MediaBrowser.Api/IHasDtoOptions.cs14
-rw-r--r--MediaBrowser.Api/Images/ImageService.cs13
-rw-r--r--MediaBrowser.Api/ItemUpdateService.cs160
-rw-r--r--MediaBrowser.Api/Library/LibraryService.cs47
-rw-r--r--MediaBrowser.Api/LiveTv/LiveTvService.cs2
-rw-r--r--MediaBrowser.Api/Movies/CollectionService.cs5
-rw-r--r--MediaBrowser.Api/Movies/MoviesService.cs28
-rw-r--r--MediaBrowser.Api/NotificationsService.cs2
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs93
-rw-r--r--MediaBrowser.Api/Playback/Hls/BaseHlsService.cs29
-rw-r--r--MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs22
-rw-r--r--MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs5
-rw-r--r--MediaBrowser.Api/Playback/Hls/MpegDashService.cs18
-rw-r--r--MediaBrowser.Api/Playback/Hls/VideoHlsService.cs20
-rw-r--r--MediaBrowser.Api/Playback/Progressive/AudioService.cs2
-rw-r--r--MediaBrowser.Api/Playback/Progressive/VideoService.cs14
-rw-r--r--MediaBrowser.Api/Playback/StreamState.cs1
-rw-r--r--MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs1
-rw-r--r--MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs1
-rw-r--r--MediaBrowser.Api/Session/SessionsService.cs26
-rw-r--r--MediaBrowser.Api/Subtitles/SubtitleService.cs7
-rw-r--r--MediaBrowser.Api/Sync/SyncService.cs92
-rw-r--r--MediaBrowser.Api/System/ActivityLogWebSocketListener.cs4
-rw-r--r--MediaBrowser.Api/System/SystemInfoWebSocketListener.cs1
-rw-r--r--MediaBrowser.Api/UserLibrary/ArtistsService.cs9
-rw-r--r--MediaBrowser.Api/UserLibrary/GameGenresService.cs7
-rw-r--r--MediaBrowser.Api/UserLibrary/GenresService.cs7
-rw-r--r--MediaBrowser.Api/UserLibrary/MusicGenresService.cs7
-rw-r--r--MediaBrowser.Api/UserLibrary/PersonsService.cs7
-rw-r--r--MediaBrowser.Api/UserLibrary/StudiosService.cs7
-rw-r--r--MediaBrowser.Api/UserLibrary/UserLibraryService.cs45
-rw-r--r--MediaBrowser.Api/UserLibrary/YearsService.cs7
-rw-r--r--MediaBrowser.Api/UserService.cs150
-rw-r--r--MediaBrowser.Api/VideosService.cs16
-rw-r--r--MediaBrowser.Common.Implementations/BaseApplicationHost.cs2
-rw-r--r--MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs51
-rw-r--r--MediaBrowser.Common.Implementations/Configuration/ConfigurationHelper.cs (renamed from MediaBrowser.Common/Configuration/ConfigurationHelper.cs)17
-rw-r--r--MediaBrowser.Common.Implementations/Devices/DeviceId.cs5
-rw-r--r--MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs4
-rw-r--r--MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj1
-rw-r--r--MediaBrowser.Common.Implementations/Security/MBLicenseFile.cs4
-rw-r--r--MediaBrowser.Common.Implementations/Serialization/XmlSerializer.cs17
-rw-r--r--MediaBrowser.Common.Implementations/packages.config1
-rw-r--r--MediaBrowser.Common/Configuration/IConfigurationFactory.cs5
-rw-r--r--MediaBrowser.Common/Extensions/BaseExtensions.cs11
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj8
-rw-r--r--MediaBrowser.Common/Plugins/BasePlugin.cs43
-rw-r--r--MediaBrowser.Controller/Channels/Channel.cs2
-rw-r--r--MediaBrowser.Controller/Channels/ChannelAudioItem.cs5
-rw-r--r--MediaBrowser.Controller/Channels/ChannelFolderItem.cs3
-rw-r--r--MediaBrowser.Controller/Channels/ChannelVideoItem.cs3
-rw-r--r--MediaBrowser.Controller/Devices/IDeviceManager.cs8
-rw-r--r--MediaBrowser.Controller/Dto/DtoOptions.cs (renamed from MediaBrowser.Model/Dto/DtoOptions.cs)19
-rw-r--r--MediaBrowser.Controller/Entities/Audio/Audio.cs18
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs3
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicArtist.cs37
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs18
-rw-r--r--MediaBrowser.Controller/Entities/Book.cs3
-rw-r--r--MediaBrowser.Controller/Entities/CollectionFolder.cs16
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs8
-rw-r--r--MediaBrowser.Controller/Entities/Game.cs3
-rw-r--r--MediaBrowser.Controller/Entities/GameSystem.cs3
-rw-r--r--MediaBrowser.Controller/Entities/IHasMediaSources.cs4
-rw-r--r--MediaBrowser.Controller/Entities/Movies/BoxSet.cs14
-rw-r--r--MediaBrowser.Controller/Entities/Movies/Movie.cs12
-rw-r--r--MediaBrowser.Controller/Entities/MusicVideo.cs3
-rw-r--r--MediaBrowser.Controller/Entities/Photo.cs5
-rw-r--r--MediaBrowser.Controller/Entities/PhotoAlbum.cs5
-rw-r--r--MediaBrowser.Controller/Entities/TV/Episode.cs61
-rw-r--r--MediaBrowser.Controller/Entities/TV/Season.cs79
-rw-r--r--MediaBrowser.Controller/Entities/TV/Series.cs15
-rw-r--r--MediaBrowser.Controller/Entities/Trailer.cs3
-rw-r--r--MediaBrowser.Controller/Entities/User.cs72
-rw-r--r--MediaBrowser.Controller/Entities/UserView.cs3
-rw-r--r--MediaBrowser.Controller/Entities/Video.cs17
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs37
-rw-r--r--MediaBrowser.Controller/Library/IUserDataManager.cs4
-rw-r--r--MediaBrowser.Controller/Library/IUserManager.cs29
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs3
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvChannel.cs3
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvProgram.cs3
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs3
-rw-r--r--MediaBrowser.Controller/LiveTv/RecordingGroup.cs3
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj11
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs91
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingOptions.cs80
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingResult.cs13
-rw-r--r--MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs22
-rw-r--r--MediaBrowser.Controller/MediaEncoding/MediaStreamSelector.cs12
-rw-r--r--MediaBrowser.Controller/MediaEncoding/VideoEncodingOptions.cs26
-rw-r--r--MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs (renamed from MediaBrowser.Common/Net/BasePeriodicWebSocketListener.cs)5
-rw-r--r--MediaBrowser.Controller/Net/IWebSocket.cs (renamed from MediaBrowser.Common/Net/IWebSocket.cs)2
-rw-r--r--MediaBrowser.Controller/Net/IWebSocketConnection.cs (renamed from MediaBrowser.Common/Net/IWebSocketConnection.cs)2
-rw-r--r--MediaBrowser.Controller/Net/IWebSocketListener.cs (renamed from MediaBrowser.Common/Net/IWebSocketListener.cs)5
-rw-r--r--MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs (renamed from MediaBrowser.Common/Net/WebSocketConnectEventArgs.cs)2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessageInfo.cs (renamed from MediaBrowser.Common/Net/WebSocketMessageInfo.cs)2
-rw-r--r--MediaBrowser.Controller/Providers/DirectoryService.cs5
-rw-r--r--MediaBrowser.Controller/Sync/ISyncManager.cs31
-rw-r--r--MediaBrowser.Controller/Sync/ISyncProvider.cs12
-rw-r--r--MediaBrowser.Dlna/Didl/DidlBuilder.cs1
-rw-r--r--MediaBrowser.Dlna/Profiles/Windows81Profile.cs55
-rw-r--r--MediaBrowser.Dlna/Profiles/WindowsPhoneProfile.cs38
-rw-r--r--MediaBrowser.LocalMetadata/BaseXmlProvider.cs8
-rw-r--r--MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs19
-rw-r--r--MediaBrowser.LocalMetadata/Savers/FolderXmlSaver.cs2
-rw-r--r--MediaBrowser.LocalMetadata/Savers/MovieXmlSaver.cs13
-rw-r--r--MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs5
-rw-r--r--MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs45
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs86
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs1049
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs434
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs830
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs16
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/JobLogger.cs122
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs71
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs177
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj7
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs4
-rw-r--r--MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj55
-rw-r--r--MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj51
-rw-r--r--MediaBrowser.Model/ApiClient/IApiClient.cs73
-rw-r--r--MediaBrowser.Model/Configuration/CinemaModeConfiguration.cs1
-rw-r--r--MediaBrowser.Model/Configuration/EncodingOptions.cs19
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs32
-rw-r--r--MediaBrowser.Model/Configuration/UserConfiguration.cs13
-rw-r--r--MediaBrowser.Model/Devices/DeviceQuery.cs5
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs6
-rw-r--r--MediaBrowser.Model/Dlna/StreamInfo.cs15
-rw-r--r--MediaBrowser.Model/Dto/BaseItemDto.cs13
-rw-r--r--MediaBrowser.Model/Dto/MetadataEditorInfo.cs27
-rw-r--r--MediaBrowser.Model/Dto/NameValuePair.cs17
-rw-r--r--MediaBrowser.Model/Dto/StreamOptions.cs67
-rw-r--r--MediaBrowser.Model/Dto/VideoStreamOptions.cs58
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj16
-rw-r--r--MediaBrowser.Model/Net/HttpResponse.cs64
-rw-r--r--MediaBrowser.Model/Net/MimeTypes.cs (renamed from MediaBrowser.Common/Net/MimeTypes.cs)153
-rw-r--r--MediaBrowser.Model/Querying/ItemFields.cs32
-rw-r--r--MediaBrowser.Model/Session/TranscodingInfo.cs2
-rw-r--r--MediaBrowser.Model/Sync/DeviceFileInfo.cs9
-rw-r--r--MediaBrowser.Model/Sync/ItemFIleInfo.cs28
-rw-r--r--MediaBrowser.Model/Sync/ItemFileType.cs19
-rw-r--r--MediaBrowser.Model/Sync/LocalItem.cs33
-rw-r--r--MediaBrowser.Model/Sync/SyncDataRequest.cs16
-rw-r--r--MediaBrowser.Model/Sync/SyncDataResponse.cs14
-rw-r--r--MediaBrowser.Model/Sync/SyncHelper.cs1
-rw-r--r--MediaBrowser.Model/Sync/SyncJob.cs1
-rw-r--r--MediaBrowser.Model/Sync/SyncJobItem.cs22
-rw-r--r--MediaBrowser.Model/Sync/SyncJobItemQuery.cs16
-rw-r--r--MediaBrowser.Model/Sync/SyncJobItemStatus.cs6
-rw-r--r--MediaBrowser.Model/Sync/SyncJobQuery.cs5
-rw-r--r--MediaBrowser.Model/Sync/SyncQuality.cs6
-rw-r--r--MediaBrowser.Model/Sync/SyncedItem.cs38
-rw-r--r--MediaBrowser.Model/Users/UserAction.cs15
-rw-r--r--MediaBrowser.Model/Users/UserActionType.cs8
-rw-r--r--MediaBrowser.Model/Users/UserPolicy.cs69
-rw-r--r--MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Manager/ImageSaver.cs1
-rw-r--r--MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs5
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs23
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs7
-rw-r--r--MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs4
-rw-r--r--MediaBrowser.Providers/Music/AlbumMetadataService.cs20
-rw-r--r--MediaBrowser.Providers/Music/ArtistMetadataService.cs21
-rw-r--r--MediaBrowser.Providers/Music/FanArtAlbumProvider.cs4
-rw-r--r--MediaBrowser.Providers/Music/FanArtArtistProvider.cs4
-rw-r--r--MediaBrowser.Providers/People/TvdbPersonImageProvider.cs4
-rw-r--r--MediaBrowser.Providers/TV/FanArtSeasonProvider.cs4
-rw-r--r--MediaBrowser.Providers/TV/FanartSeriesProvider.cs4
-rw-r--r--MediaBrowser.Providers/TV/MissingEpisodeProvider.cs63
-rw-r--r--MediaBrowser.Providers/TV/MovieDbSeasonProvider.cs8
-rw-r--r--MediaBrowser.Providers/TV/SeriesMetadataService.cs25
-rw-r--r--MediaBrowser.Providers/TV/SeriesPostScanTask.cs15
-rw-r--r--MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs15
-rw-r--r--MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs4
-rw-r--r--MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs4
-rw-r--r--MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Channels/ChannelManager.cs55
-rw-r--r--MediaBrowser.Server.Implementations/Channels/ChannelPostScanTask.cs37
-rw-r--r--MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs49
-rw-r--r--MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Connect/ConnectManager.cs38
-rw-r--r--MediaBrowser.Server.Implementations/Devices/DeviceManager.cs44
-rw-r--r--MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs7
-rw-r--r--MediaBrowser.Server.Implementations/Dto/DtoService.cs45
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/UsageEntryPoint.cs7
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs14
-rw-r--r--MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs32
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs16
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs2
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/IHttpListener.cs4
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs6
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs2
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs64
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SocketSharp/Extensions.cs6
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs16
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SocketSharpLogger.cs2
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs77
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs2
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Library/LibraryManager.cs212
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs63
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs18
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs50
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs18
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs120
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs12
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs4
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs79
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs15
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs5
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs479
-rw-r--r--MediaBrowser.Server.Implementations/Library/SearchEngine.cs17
-rw-r--r--MediaBrowser.Server.Implementations/Library/UserDataManager.cs76
-rw-r--r--MediaBrowser.Server.Implementations/Library/UserManager.cs249
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs21
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/da.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/de.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/el.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/es.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/fi.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json44
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/he.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/hr.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/it.json56
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json36
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json42
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json38
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/tr.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/zh_CN.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json34
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/ar.json33
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/ca.json33
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/cs.json29
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/da.json29
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/de.json33
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/el.json33
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/en_GB.json33
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/en_US.json33
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/es.json29
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/es_MX.json39
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/fi.json33
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/fr.json45
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/he.json29
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/hr.json29
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/it.json49
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/kk.json39
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/ko.json33
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/ms.json33
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/nb.json37
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/nl.json35
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/pl.json33
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json33
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json43
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/ru.json69
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/server.json35
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/sv.json29
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/tr.json33
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/vi.json33
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/zh_CN.json47
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json29
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj11
-rw-r--r--MediaBrowser.Server.Implementations/News/NewsService.cs8
-rw-r--r--MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs2
-rw-r--r--MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionManager.cs88
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs1
-rw-r--r--MediaBrowser.Server.Implementations/Session/WebSocketController.cs13
-rw-r--r--MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs20
-rw-r--r--MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs8
-rw-r--r--MediaBrowser.Server.Implementations/Sync/MockSyncProvider.cs33
-rw-r--r--MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs190
-rw-r--r--MediaBrowser.Server.Implementations/Sync/SyncManager.cs321
-rw-r--r--MediaBrowser.Server.Implementations/Sync/SyncRepository.cs107
-rw-r--r--MediaBrowser.Server.Implementations/Sync/SyncScheduledTask.cs12
-rw-r--r--MediaBrowser.Server.Implementations/packages.config2
-rw-r--r--MediaBrowser.Server.Mac/Native/BaseMonoApp.cs2
-rw-r--r--MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj4
-rw-r--r--MediaBrowser.Server.Mono/Native/BaseMonoApp.cs2
-rw-r--r--MediaBrowser.Server.Startup.Common/ApplicationHost.cs123
-rw-r--r--MediaBrowser.Server.Startup.Common/INativeApp.cs5
-rw-r--r--MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj2
-rw-r--r--MediaBrowser.Server.Startup.Common/Migrations/MigrateTranscodingPath.cs30
-rw-r--r--MediaBrowser.Server.Startup.Common/Migrations/PlaylistImages.cs55
-rw-r--r--MediaBrowser.Server.Startup.Common/Migrations/RenameXmlOptions.cs4
-rw-r--r--MediaBrowser.ServerApplication/Native/RegisterServer.bat18
-rw-r--r--MediaBrowser.ServerApplication/Native/ServerAuthorization.cs9
-rw-r--r--MediaBrowser.ServerApplication/Native/WindowsApp.cs4
-rw-r--r--MediaBrowser.Tests/MediaBrowser.Tests.csproj1
-rw-r--r--MediaBrowser.Tests/Resolvers/TvUtilTests.cs246
-rw-r--r--MediaBrowser.WebDashboard/Api/DashboardService.cs1
-rw-r--r--MediaBrowser.WebDashboard/Api/PackageCreator.cs5
-rw-r--r--MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj788
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs2
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs2
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs17
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs2
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs12
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs2
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs2
-rw-r--r--MediaBrowser.sln5
-rw-r--r--Nuget/MediaBrowser.Common.Internal.nuspec5
-rw-r--r--Nuget/MediaBrowser.Common.nuspec2
-rw-r--r--Nuget/MediaBrowser.Model.Signed.nuspec2
-rw-r--r--Nuget/MediaBrowser.Server.Core.nuspec4
-rw-r--r--SharedVersion.cs2
323 files changed, 8496 insertions, 3919 deletions
diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs
index d386373d4..0eb92d5a7 100644
--- a/MediaBrowser.Api/ApiEntryPoint.cs
+++ b/MediaBrowser.Api/ApiEntryPoint.cs
@@ -1,7 +1,10 @@
using MediaBrowser.Api.Playback;
-using MediaBrowser.Controller;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Session;
using System;
@@ -33,7 +36,7 @@ namespace MediaBrowser.Api
/// <summary>
/// The application paths
/// </summary>
- private readonly IServerApplicationPaths _appPaths;
+ private readonly IServerConfigurationManager _config;
private readonly ISessionManager _sessionManager;
@@ -43,13 +46,13 @@ namespace MediaBrowser.Api
/// Initializes a new instance of the <see cref="ApiEntryPoint" /> class.
/// </summary>
/// <param name="logger">The logger.</param>
- /// <param name="appPaths">The application paths.</param>
/// <param name="sessionManager">The session manager.</param>
- public ApiEntryPoint(ILogger logger, IServerApplicationPaths appPaths, ISessionManager sessionManager)
+ /// <param name="config">The configuration.</param>
+ public ApiEntryPoint(ILogger logger, ISessionManager sessionManager, IServerConfigurationManager config)
{
Logger = logger;
- _appPaths = appPaths;
_sessionManager = sessionManager;
+ _config = config;
Instance = this;
}
@@ -73,12 +76,19 @@ namespace MediaBrowser.Api
}
}
+ public EncodingOptions GetEncodingOptions()
+ {
+ return _config.GetConfiguration<EncodingOptions>("encoding");
+ }
+
/// <summary>
/// Deletes the encoded media cache.
/// </summary>
private void DeleteEncodedMediaCache()
{
- foreach (var file in Directory.EnumerateFiles(_appPaths.TranscodingTempPath, "*", SearchOption.AllDirectories)
+ var path = Path.Combine(_config.ApplicationPaths.TranscodingTempPath, EncodingContext.Streaming.ToString().ToLower());
+
+ foreach (var file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)
.ToList())
{
File.Delete(file);
@@ -185,7 +195,9 @@ namespace MediaBrowser.Api
CompletionPercentage = percentComplete,
Width = state.OutputWidth,
Height = state.OutputHeight,
- AudioChannels = state.OutputAudioChannels
+ AudioChannels = state.OutputAudioChannels,
+ IsAudioDirect = string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase),
+ IsVideoDirect = string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)
});
}
}
diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs
index 5fe606e16..3eb0296fc 100644
--- a/MediaBrowser.Api/ConfigurationService.cs
+++ b/MediaBrowser.Api/ConfigurationService.cs
@@ -123,7 +123,7 @@ namespace MediaBrowser.Api
public void Post(AutoSetMetadataOptions request)
{
- _configurationManager.DisableMetadataService("Media Browser Legacy Xml");
+ _configurationManager.DisableMetadataService("Media Browser Xml");
_configurationManager.SaveConfiguration();
}
diff --git a/MediaBrowser.Api/Dlna/DlnaServerService.cs b/MediaBrowser.Api/Dlna/DlnaServerService.cs
index 9981d506e..94d6e25b0 100644
--- a/MediaBrowser.Api/Dlna/DlnaServerService.cs
+++ b/MediaBrowser.Api/Dlna/DlnaServerService.cs
@@ -1,5 +1,4 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Controller.Dlna;
using ServiceStack;
using ServiceStack.Text.Controller;
using ServiceStack.Web;
@@ -77,14 +76,11 @@ namespace MediaBrowser.Api.Dlna
private readonly IContentDirectory _contentDirectory;
private readonly IConnectionManager _connectionManager;
- private readonly IConfigurationManager _config;
-
- public DlnaServerService(IDlnaManager dlnaManager, IContentDirectory contentDirectory, IConnectionManager connectionManager, IConfigurationManager config)
+ public DlnaServerService(IDlnaManager dlnaManager, IContentDirectory contentDirectory, IConnectionManager connectionManager)
{
_dlnaManager = dlnaManager;
_contentDirectory = contentDirectory;
_connectionManager = connectionManager;
- _config = config;
}
public object Get(GetDescriptionXml request)
diff --git a/MediaBrowser.Api/IHasDtoOptions.cs b/MediaBrowser.Api/IHasDtoOptions.cs
index f7fb57f01..7fe47c4a1 100644
--- a/MediaBrowser.Api/IHasDtoOptions.cs
+++ b/MediaBrowser.Api/IHasDtoOptions.cs
@@ -1,4 +1,4 @@
-using MediaBrowser.Model.Dto;
+using MediaBrowser.Controller.Dto;
using MediaBrowser.Model.Entities;
using System;
using System.Linq;
@@ -28,17 +28,7 @@ namespace MediaBrowser.Api
options.ImageTypeLimit = request.ImageTypeLimit.Value;
}
- if (string.IsNullOrWhiteSpace(request.EnableImageTypes))
- {
- if (options.EnableImages)
- {
- // Get everything
- options.ImageTypes = Enum.GetNames(typeof(ImageType))
- .Select(i => (ImageType)Enum.Parse(typeof(ImageType), i, true))
- .ToList();
- }
- }
- else
+ if (!string.IsNullOrWhiteSpace(request.EnableImageTypes))
{
options.ImageTypes = (request.EnableImageTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)).ToList();
}
diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs
index 0e4ccf0b1..f2586b043 100644
--- a/MediaBrowser.Api/Images/ImageService.cs
+++ b/MediaBrowser.Api/Images/ImageService.cs
@@ -18,6 +18,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
namespace MediaBrowser.Api.Images
{
@@ -668,26 +669,26 @@ namespace MediaBrowser.Api.Images
{
if (format == ImageFormat.Bmp)
{
- return Common.Net.MimeTypes.GetMimeType("i.bmp");
+ return MimeTypes.GetMimeType("i.bmp");
}
if (format == ImageFormat.Gif)
{
- return Common.Net.MimeTypes.GetMimeType("i.gif");
+ return MimeTypes.GetMimeType("i.gif");
}
if (format == ImageFormat.Jpg)
{
- return Common.Net.MimeTypes.GetMimeType("i.jpg");
+ return MimeTypes.GetMimeType("i.jpg");
}
if (format == ImageFormat.Png)
{
- return Common.Net.MimeTypes.GetMimeType("i.png");
+ return MimeTypes.GetMimeType("i.png");
}
if (format == ImageFormat.Webp)
{
- return Common.Net.MimeTypes.GetMimeType("i.webp");
+ return MimeTypes.GetMimeType("i.webp");
}
- return Common.Net.MimeTypes.GetMimeType(path);
+ return MimeTypes.GetMimeType(path);
}
/// <summary>
diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs
index 65c51beff..272dff3ec 100644
--- a/MediaBrowser.Api/ItemUpdateService.cs
+++ b/MediaBrowser.Api/ItemUpdateService.cs
@@ -1,9 +1,14 @@
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Net;
+using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
using ServiceStack;
using System;
using System.Collections.Generic;
@@ -20,14 +25,165 @@ namespace MediaBrowser.Api
public string ItemId { get; set; }
}
+ [Route("/Items/{ItemId}/MetadataEditor", "GET", Summary = "Gets metadata editor info for an item")]
+ public class GetMetadataEditorInfo : IReturn<MetadataEditorInfo>
+ {
+ [ApiMember(Name = "ItemId", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string ItemId { get; set; }
+ }
+
+ [Route("/Items/{ItemId}/ContentType", "POST", Summary = "Updates an item's content type")]
+ public class UpdateItemContentType : IReturnVoid
+ {
+ [ApiMember(Name = "ItemId", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+ public string ItemId { get; set; }
+
+ [ApiMember(Name = "ContentType", Description = "The content type of the item", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
+ public string ContentType { get; set; }
+ }
+
[Authenticated]
public class ItemUpdateService : BaseApiService
{
private readonly ILibraryManager _libraryManager;
+ private readonly IProviderManager _providerManager;
+ private readonly ILocalizationManager _localizationManager;
+ private readonly IServerConfigurationManager _config;
- public ItemUpdateService(ILibraryManager libraryManager)
+ public ItemUpdateService(ILibraryManager libraryManager, IProviderManager providerManager, ILocalizationManager localizationManager, IServerConfigurationManager config)
{
_libraryManager = libraryManager;
+ _providerManager = providerManager;
+ _localizationManager = localizationManager;
+ _config = config;
+ }
+
+ public object Get(GetMetadataEditorInfo request)
+ {
+ var item = _libraryManager.GetItemById(request.ItemId);
+
+ var info = new MetadataEditorInfo
+ {
+ ParentalRatingOptions = _localizationManager.GetParentalRatings().ToList(),
+ ExternalIdInfos = _providerManager.GetExternalIdInfos(item).ToList(),
+ Countries = _localizationManager.GetCountries().ToList(),
+ Cultures = _localizationManager.GetCultures().ToList()
+ };
+
+ var locationType = item.LocationType;
+ if (locationType == LocationType.FileSystem ||
+ locationType == LocationType.Offline)
+ {
+ if (!(item is ICollectionFolder) && !(item is UserView) && !(item is AggregateFolder) && !(item is LiveTvChannel) && !(item is IItemByName))
+ {
+ var collectionType = _libraryManager.GetInheritedContentType(item);
+ if (string.IsNullOrWhiteSpace(collectionType))
+ {
+ info.ContentTypeOptions = GetContentTypeOptions(true);
+ info.ContentType = _libraryManager.GetContentType(item);
+ }
+ }
+ }
+
+ return ToOptimizedResult(info);
+ }
+
+ public void Post(UpdateItemContentType request)
+ {
+ var item = _libraryManager.GetItemById(request.ItemId);
+ var path = item.ContainingFolderPath;
+
+ var types = _config.Configuration.ContentTypes
+ .Where(i => !string.Equals(i.Name, path, StringComparison.OrdinalIgnoreCase))
+ .ToList();
+
+ if (!string.IsNullOrWhiteSpace(request.ContentType))
+ {
+ types.Add(new NameValuePair
+ {
+ Name = path,
+ Value = request.ContentType
+ });
+ }
+
+ _config.Configuration.ContentTypes = types.ToArray();
+ _config.SaveConfiguration();
+ }
+
+ private List<NameValuePair> GetContentTypeOptions(bool isForItem)
+ {
+ var list = new List<NameValuePair>();
+
+ if (isForItem)
+ {
+ list.Add(new NameValuePair
+ {
+ Name = "FolderTypeInherit",
+ Value = ""
+ });
+ }
+
+ list.Add(new NameValuePair
+ {
+ Name = "FolderTypeMovies",
+ Value = "movies"
+ });
+ list.Add(new NameValuePair
+ {
+ Name = "FolderTypeMusic",
+ Value = "music"
+ });
+ list.Add(new NameValuePair
+ {
+ Name = "FolderTypeTvShows",
+ Value = "tvshows"
+ });
+
+ if (!isForItem)
+ {
+ list.Add(new NameValuePair
+ {
+ Name = "FolderTypeBooks",
+ Value = "books"
+ });
+ list.Add(new NameValuePair
+ {
+ Name = "FolderTypeGames",
+ Value = "games"
+ });
+ }
+
+ list.Add(new NameValuePair
+ {
+ Name = "FolderTypeHomeVideos",
+ Value = "homevideos"
+ });
+ list.Add(new NameValuePair
+ {
+ Name = "FolderTypeMusicVideos",
+ Value = "musicvideos"
+ });
+ list.Add(new NameValuePair
+ {
+ Name = "FolderTypePhotos",
+ Value = "photos"
+ });
+
+ if (!isForItem)
+ {
+ list.Add(new NameValuePair
+ {
+ Name = "FolderTypeMixed",
+ Value = ""
+ });
+ }
+
+ foreach (var val in list)
+ {
+ val.Name = _localizationManager.GetLocalizedString(val.Name);
+ }
+
+ return list;
}
public void Post(UpdateItem request)
diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs
index 5cb007f8f..5e1619672 100644
--- a/MediaBrowser.Api/Library/LibraryService.cs
+++ b/MediaBrowser.Api/Library/LibraryService.cs
@@ -272,16 +272,13 @@ namespace MediaBrowser.Api.Library
items = items.Where(i => i.IsHidden == val).ToList();
}
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields))
- .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
- .ToList();
+ var dtoOptions = new DtoOptions();
var result = new ItemsResult
{
TotalRecordCount = items.Count,
- Items = items.Select(i => _dtoService.GetBaseItemDto(i, fields)).ToArray()
+ Items = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions)).ToArray()
};
return ToOptimizedResult(result);
@@ -347,10 +344,7 @@ namespace MediaBrowser.Api.Library
var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null;
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields))
- .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
- .ToList();
+ var dtoOptions = new DtoOptions();
BaseItem parent = item.Parent;
@@ -361,7 +355,7 @@ namespace MediaBrowser.Api.Library
parent = TranslateParentItem(parent, user);
}
- baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, fields, user));
+ baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user));
parent = parent.Parent;
}
@@ -473,20 +467,20 @@ namespace MediaBrowser.Api.Library
var auth = _authContext.GetAuthorizationInfo(Request);
var user = _userManager.GetUserById(auth.UserId);
- if (item is Playlist)
+ if (item is Playlist || item is BoxSet)
{
// For now this is allowed if user can see the playlist
}
else if (item is ILiveTvRecording)
{
- if (!user.Configuration.EnableLiveTvManagement)
+ if (!user.Policy.EnableLiveTvManagement)
{
throw new UnauthorizedAccessException();
}
}
else
{
- if (!user.Configuration.EnableContentDeletion)
+ if (!user.Policy.EnableContentDeletion)
{
throw new UnauthorizedAccessException();
}
@@ -583,11 +577,6 @@ namespace MediaBrowser.Api.Library
item = item.Parent;
}
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields))
- .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
- .ToList();
-
var themeSongIds = GetThemeSongIds(item);
if (themeSongIds.Count == 0 && request.InheritFromParent)
@@ -607,10 +596,12 @@ namespace MediaBrowser.Api.Library
}
}
}
-
+
+ var dtoOptions = new DtoOptions();
+
var dtos = themeSongIds.Select(_libraryManager.GetItemById)
.OrderBy(i => i.SortName)
- .Select(i => _dtoService.GetBaseItemDto(i, fields, user, item));
+ .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
var items = dtos.ToArray();
@@ -651,11 +642,6 @@ namespace MediaBrowser.Api.Library
item = item.Parent;
}
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields))
- .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
- .ToList();
-
var themeVideoIds = GetThemeVideoIds(item);
if (themeVideoIds.Count == 0 && request.InheritFromParent)
@@ -681,9 +667,11 @@ namespace MediaBrowser.Api.Library
}
}
+ var dtoOptions = new DtoOptions();
+
var dtos = themeVideoIds.Select(_libraryManager.GetItemById)
.OrderBy(i => i.SortName)
- .Select(i => _dtoService.GetBaseItemDto(i, fields, user, item));
+ .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
var items = dtos.ToArray();
@@ -754,10 +742,7 @@ namespace MediaBrowser.Api.Library
: (Folder)_libraryManager.RootFolder)
: _libraryManager.GetItemById(id);
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields))
- .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
- .ToList();
+ var dtoOptions = new DtoOptions();
var dtos = GetSoundtrackSongIds(item, inheritFromParent)
.Select(_libraryManager.GetItemById)
@@ -765,7 +750,7 @@ namespace MediaBrowser.Api.Library
.SelectMany(i => i.RecursiveChildren)
.OfType<Audio>()
.OrderBy(i => i.SortName)
- .Select(i => _dtoService.GetBaseItemDto(i, fields, user, item));
+ .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
var items = dtos.ToArray();
diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs
index 3afe72866..f3dcf57e0 100644
--- a/MediaBrowser.Api/LiveTv/LiveTvService.cs
+++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs
@@ -319,7 +319,7 @@ namespace MediaBrowser.Api.LiveTv
throw new UnauthorizedAccessException("Anonymous live tv management is not allowed.");
}
- if (!user.Configuration.EnableLiveTvManagement)
+ if (!user.Policy.EnableLiveTvManagement)
{
throw new UnauthorizedAccessException("The current user does not have permission to manage live tv.");
}
diff --git a/MediaBrowser.Api/Movies/CollectionService.cs b/MediaBrowser.Api/Movies/CollectionService.cs
index 346c085f8..97c6cd87d 100644
--- a/MediaBrowser.Api/Movies/CollectionService.cs
+++ b/MediaBrowser.Api/Movies/CollectionService.cs
@@ -2,6 +2,7 @@
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Collections;
+using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying;
using ServiceStack;
using System;
@@ -70,7 +71,9 @@ namespace MediaBrowser.Api.Movies
}).ConfigureAwait(false);
- var dto = _dtoService.GetBaseItemDto(item, new List<ItemFields>());
+ var dtoOptions = new DtoOptions();
+
+ var dto = _dtoService.GetBaseItemDto(item, dtoOptions);
return ToOptimizedResult(new CollectionCreationResult
{
diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs
index fc03ab466..ba3c15a90 100644
--- a/MediaBrowser.Api/Movies/MoviesService.cs
+++ b/MediaBrowser.Api/Movies/MoviesService.cs
@@ -157,7 +157,11 @@ namespace MediaBrowser.Api.Movies
.DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString(), StringComparer.OrdinalIgnoreCase)
.ToList();
- var result = GetRecommendationCategories(user, listEligibleForCategories, listEligibleForSuggestion, request.CategoryLimit, request.ItemLimit, request.GetItemFields().ToList());
+ var dtoOptions = new DtoOptions();
+
+ dtoOptions.Fields = request.GetItemFields().ToList();
+
+ var result = GetRecommendationCategories(user, listEligibleForCategories, listEligibleForSuggestion, request.CategoryLimit, request.ItemLimit, dtoOptions);
return ToOptimizedResult(result);
}
@@ -232,7 +236,7 @@ namespace MediaBrowser.Api.Movies
return result;
}
- private IEnumerable<RecommendationDto> GetRecommendationCategories(User user, List<BaseItem> allMoviesForCategories, List<BaseItem> allMovies, int categoryLimit, int itemLimit, List<ItemFields> fields)
+ private IEnumerable<RecommendationDto> GetRecommendationCategories(User user, List<BaseItem> allMoviesForCategories, List<BaseItem> allMovies, int categoryLimit, int itemLimit, DtoOptions dtoOptions)
{
var categories = new List<RecommendationDto>();
@@ -282,11 +286,11 @@ namespace MediaBrowser.Api.Movies
.OrderBy(i => Guid.NewGuid())
.ToList();
- var similarToRecentlyPlayed = GetSimilarTo(user, allMovies, recentlyPlayedMovies.Take(7).OrderBy(i => Guid.NewGuid()), itemLimit, fields, RecommendationType.SimilarToRecentlyPlayed).GetEnumerator();
- var similarToLiked = GetSimilarTo(user, allMovies, likedMovies, itemLimit, fields, RecommendationType.SimilarToLikedItem).GetEnumerator();
+ var similarToRecentlyPlayed = GetSimilarTo(user, allMovies, recentlyPlayedMovies.Take(7).OrderBy(i => Guid.NewGuid()), itemLimit, dtoOptions, RecommendationType.SimilarToRecentlyPlayed).GetEnumerator();
+ var similarToLiked = GetSimilarTo(user, allMovies, likedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToLikedItem).GetEnumerator();
- var hasDirectorFromRecentlyPlayed = GetWithDirector(user, allMovies, recentDirectors, itemLimit, fields, RecommendationType.HasDirectorFromRecentlyPlayed).GetEnumerator();
- var hasActorFromRecentlyPlayed = GetWithActor(user, allMovies, recentActors, itemLimit, fields, RecommendationType.HasActorFromRecentlyPlayed).GetEnumerator();
+ var hasDirectorFromRecentlyPlayed = GetWithDirector(user, allMovies, recentDirectors, itemLimit, dtoOptions, RecommendationType.HasDirectorFromRecentlyPlayed).GetEnumerator();
+ var hasActorFromRecentlyPlayed = GetWithActor(user, allMovies, recentActors, itemLimit, dtoOptions, RecommendationType.HasActorFromRecentlyPlayed).GetEnumerator();
var categoryTypes = new List<IEnumerator<RecommendationDto>>
{
@@ -329,7 +333,7 @@ namespace MediaBrowser.Api.Movies
return categories.OrderBy(i => i.RecommendationType).ThenBy(i => Guid.NewGuid());
}
- private IEnumerable<RecommendationDto> GetWithDirector(User user, List<BaseItem> allMovies, IEnumerable<string> directors, int itemLimit, List<ItemFields> fields, RecommendationType type)
+ private IEnumerable<RecommendationDto> GetWithDirector(User user, List<BaseItem> allMovies, IEnumerable<string> directors, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
{
var userId = user.Id;
@@ -347,13 +351,13 @@ namespace MediaBrowser.Api.Movies
BaselineItemName = director,
CategoryId = director.GetMD5().ToString("N"),
RecommendationType = type,
- Items = items.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray()
+ Items = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray()
};
}
}
}
- private IEnumerable<RecommendationDto> GetWithActor(User user, List<BaseItem> allMovies, IEnumerable<string> names, int itemLimit, List<ItemFields> fields, RecommendationType type)
+ private IEnumerable<RecommendationDto> GetWithActor(User user, List<BaseItem> allMovies, IEnumerable<string> names, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
{
var userId = user.Id;
@@ -371,13 +375,13 @@ namespace MediaBrowser.Api.Movies
BaselineItemName = name,
CategoryId = name.GetMD5().ToString("N"),
RecommendationType = type,
- Items = items.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray()
+ Items = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray()
};
}
}
}
- private IEnumerable<RecommendationDto> GetSimilarTo(User user, List<BaseItem> allMovies, IEnumerable<BaseItem> baselineItems, int itemLimit, List<ItemFields> fields, RecommendationType type)
+ private IEnumerable<RecommendationDto> GetSimilarTo(User user, List<BaseItem> allMovies, IEnumerable<BaseItem> baselineItems, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
{
var userId = user.Id;
@@ -395,7 +399,7 @@ namespace MediaBrowser.Api.Movies
BaselineItemName = item.Name,
CategoryId = item.Id.ToString("N"),
RecommendationType = type,
- Items = similar.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray()
+ Items = similar.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray()
};
}
}
diff --git a/MediaBrowser.Api/NotificationsService.cs b/MediaBrowser.Api/NotificationsService.cs
index 69f1f3489..5103d1b5c 100644
--- a/MediaBrowser.Api/NotificationsService.cs
+++ b/MediaBrowser.Api/NotificationsService.cs
@@ -135,7 +135,7 @@ namespace MediaBrowser.Api
Level = request.Level,
Name = request.Name,
Url = request.Url,
- UserIds = _userManager.Users.Where(i => i.Configuration.IsAdministrator).Select(i => i.Id.ToString("N")).ToList()
+ UserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id.ToString("N")).ToList()
};
await _notificationManager.SendNotification(notification, CancellationToken.None).ConfigureAwait(false);
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 12ccfb6b1..8662e64b4 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
@@ -118,8 +119,8 @@ namespace MediaBrowser.Api.Playback
/// <returns>System.String.</returns>
private string GetOutputFilePath(StreamState state)
{
- var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath;
-
+ var folder = Path.Combine(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, EncodingContext.Streaming.ToString().ToLower());
+
var outputFileExtension = GetOutputFileExtension(state);
var data = GetCommandLineArguments("dummy\\dummy", "dummyTranscodingId", state, false);
@@ -202,6 +203,10 @@ namespace MediaBrowser.Api.Playback
{
args += " -map -0:s";
}
+ else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
+ {
+ args += " -map 1:0 -sn";
+ }
return args;
}
@@ -245,7 +250,7 @@ namespace MediaBrowser.Api.Playback
protected EncodingQuality GetQualitySetting()
{
- var quality = ServerConfigurationManager.Configuration.MediaEncodingQuality;
+ var quality = ApiEntryPoint.Instance.GetEncodingOptions().EncodingQuality;
if (quality == EncodingQuality.Auto)
{
@@ -273,7 +278,7 @@ namespace MediaBrowser.Api.Playback
// Recommended per docs
return Math.Max(Environment.ProcessorCount - 1, 2);
}
-
+
// Use more when this is true. -re will keep cpu usage under control
if (state.ReadInputAtNativeFramerate)
{
@@ -302,6 +307,21 @@ namespace MediaBrowser.Api.Playback
}
}
+ protected string H264Encoder
+ {
+ get
+ {
+ var lib = ApiEntryPoint.Instance.GetEncodingOptions().H264Encoder;
+
+ if (!string.IsNullOrWhiteSpace(lib))
+ {
+ return lib;
+ }
+
+ return "libx264";
+ }
+ }
+
/// <summary>
/// Gets the video bitrate to specify on the command line
/// </summary>
@@ -318,7 +338,7 @@ namespace MediaBrowser.Api.Playback
var qualitySetting = GetQualitySetting();
- if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(videoCodec, H264Encoder, StringComparison.OrdinalIgnoreCase))
{
switch (qualitySetting)
{
@@ -442,7 +462,7 @@ namespace MediaBrowser.Api.Playback
{
if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5)
{
- volParam = ",volume=" + ServerConfigurationManager.Configuration.DownMixAudioBoost.ToString(UsCulture);
+ volParam = ",volume=" + ApiEntryPoint.Instance.GetEncodingOptions().DownMixAudioBoost.ToString(UsCulture);
}
}
@@ -651,9 +671,18 @@ namespace MediaBrowser.Api.Playback
videoSizeParam = string.Format(",scale={0}:{1}", state.VideoStream.Width.Value.ToString(UsCulture), state.VideoStream.Height.Value.ToString(UsCulture));
}
- return string.Format(" -filter_complex \"[0:{0}]format=yuva444p{3},lut=u=128:v=128:y=gammaval(.3)[sub] ; [0:{1}] [sub] overlay{2}\"",
- state.SubtitleStream.Index,
- state.VideoStream.Index,
+ var mapPrefix = state.SubtitleStream.IsExternal ?
+ 1 :
+ 0;
+
+ var subtitleStreamIndex = state.SubtitleStream.IsExternal
+ ? 0
+ : state.SubtitleStream.Index;
+
+ return string.Format(" -filter_complex \"[{0}:{1}]format=yuva444p{4},lut=u=128:v=128:y=gammaval(.3)[sub] ; [0:{2}] [sub] overlay{3}\"",
+ mapPrefix.ToString(UsCulture),
+ subtitleStreamIndex.ToString(UsCulture),
+ state.VideoStream.Index.ToString(UsCulture),
outputSizeParam,
videoSizeParam);
}
@@ -700,7 +729,8 @@ namespace MediaBrowser.Api.Playback
return Math.Min(request.MaxAudioChannels.Value, audioStream.Channels.Value);
}
- return request.MaxAudioChannels.Value;
+ // If we don't have any media info then limit it to 5 to prevent encoding errors due to asking for too many channels
+ return Math.Min(request.MaxAudioChannels.Value, 5);
}
return request.AudioChannels;
@@ -761,7 +791,7 @@ namespace MediaBrowser.Api.Playback
{
if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
{
- return "libx264";
+ return H264Encoder;
}
if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase))
{
@@ -798,6 +828,21 @@ namespace MediaBrowser.Api.Playback
/// <returns>System.String.</returns>
protected string GetInputArgument(string transcodingJobId, StreamState state)
{
+ var arg = "-i " + GetInputPathArgument(transcodingJobId, state);
+
+ if (state.SubtitleStream != null)
+ {
+ if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
+ {
+ arg += " -i \"" + state.SubtitleStream.Path + "\"";
+ }
+ }
+
+ return arg;
+ }
+
+ private string GetInputPathArgument(string transcodingJobId, StreamState state)
+ {
if (state.InputProtocol == MediaProtocol.File &&
state.RunTimeTicks.HasValue &&
state.VideoType == VideoType.VideoFile &&
@@ -868,7 +913,7 @@ namespace MediaBrowser.Api.Playback
state.InputProtocol = streamInfo.Protocol;
await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
-
+
AttachMediaStreamInfo(state, streamInfo, state.VideoRequest, state.RequestedUrl);
checkCodecs = true;
}
@@ -898,8 +943,8 @@ namespace MediaBrowser.Api.Playback
/// <param name="cancellationTokenSource">The cancellation token source.</param>
/// <param name="workingDirectory">The working directory.</param>
/// <returns>Task.</returns>
- protected async Task<TranscodingJob> StartFfMpeg(StreamState state,
- string outputPath,
+ protected async Task<TranscodingJob> StartFfMpeg(StreamState state,
+ string outputPath,
CancellationTokenSource cancellationTokenSource,
string workingDirectory = null)
{
@@ -910,7 +955,7 @@ namespace MediaBrowser.Api.Playback
var transcodingId = Guid.NewGuid().ToString("N");
var commandLineArgs = GetCommandLineArguments(outputPath, transcodingId, state, true);
- if (ServerConfigurationManager.Configuration.EnableDebugEncodingLogging)
+ if (ApiEntryPoint.Instance.GetEncodingOptions().EnableDebugLogging)
{
commandLineArgs = "-loglevel debug " + commandLineArgs;
}
@@ -1088,7 +1133,7 @@ namespace MediaBrowser.Api.Playback
if (scale.HasValue)
{
long val;
-
+
if (long.TryParse(size, NumberStyles.Any, UsCulture, out val))
{
bytesTranscoded = val * scale.Value;
@@ -1562,9 +1607,6 @@ namespace MediaBrowser.Api.Playback
mediaStreams = new List<MediaStream>();
state.DeInterlace = true;
- state.OutputAudioSync = "1000";
- state.InputVideoSync = "-1";
- state.InputAudioSync = "1";
// Just to prevent this from being null and causing other methods to fail
state.MediaPath = string.Empty;
@@ -1630,7 +1672,7 @@ namespace MediaBrowser.Api.Playback
if (string.IsNullOrEmpty(container))
{
- container = request.Static ?
+ container = request.Static ?
state.InputContainer :
(Path.GetExtension(GetOutputFilePath(state)) ?? string.Empty).TrimStart('.');
}
@@ -1696,9 +1738,16 @@ namespace MediaBrowser.Api.Playback
state.InputFileSize = mediaSource.Size;
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
+ if (state.ReadInputAtNativeFramerate)
+ {
+ state.OutputAudioSync = "1000";
+ state.InputVideoSync = "-1";
+ state.InputAudioSync = "1";
+ }
+
AttachMediaStreamInfo(state, mediaSource.MediaStreams, videoRequest, requestedUrl);
}
-
+
private void AttachMediaStreamInfo(StreamState state,
List<MediaStream> mediaStreams,
VideoStreamRequest videoRequest,
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index c963636fd..8a33a88f2 100644
--- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
@@ -6,7 +7,6 @@ using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.IO;
using System;
using System.Collections.Generic;
@@ -14,6 +14,7 @@ using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Model.Net;
namespace MediaBrowser.Api.Playback.Hls
{
@@ -119,11 +120,7 @@ namespace MediaBrowser.Api.Playback.Hls
if (isLive)
{
- //var file = request.PlaylistId + Path.GetExtension(Request.PathInfo);
-
- //file = Path.Combine(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, file);
-
- return ResultFactory.GetStaticFileResult(Request, playlist, FileShare.ReadWrite);
+ return ResultFactory.GetResult(GetLivePlaylistText(playlist, state.SegmentLength), MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
}
var audioBitrate = state.OutputAudioBitrate ?? 0;
@@ -144,6 +141,22 @@ namespace MediaBrowser.Api.Playback.Hls
return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
}
+ private string GetLivePlaylistText(string path, int segmentLength)
+ {
+ using (var stream = FileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
+ {
+ using (var reader = new StreamReader(stream))
+ {
+ var text = reader.ReadToEnd();
+
+ var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(UsCulture) + Environment.NewLine + "#EXT-X-ALLOW-CACHE:NO";
+
+ // ffmpeg pads the reported length by a full second
+ return text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(UsCulture), newDuration, StringComparison.OrdinalIgnoreCase);
+ }
+ }
+ }
+
private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, bool includeBaselineStream, int baselineStreamBitrate)
{
var builder = new StringBuilder();
@@ -227,7 +240,7 @@ namespace MediaBrowser.Api.Playback.Hls
"hls/" + Path.GetFileNameWithoutExtension(outputPath));
}
- var args = string.Format("{0} {1} -i {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9}{10} -y \"{11}\"",
+ var args = string.Format("{0} {1} {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9}{10} -y \"{11}\"",
itsOffset,
inputModifier,
GetInputArgument(transcodingJobId, state),
diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
index fe3bd12fb..86866bdf5 100644
--- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
@@ -20,6 +20,7 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
namespace MediaBrowser.Api.Playback.Hls
{
@@ -387,7 +388,7 @@ namespace MediaBrowser.Api.Playback.Hls
playlistText = GetMasterPlaylistFileText(state, videoBitrate + audioBitrate);
}
- return ResultFactory.GetResult(playlistText, Common.Net.MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
+ return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
}
private string GetMasterPlaylistFileText(StreamState state, int totalBitrate)
@@ -603,7 +604,7 @@ namespace MediaBrowser.Api.Playback.Hls
var playlistText = builder.ToString();
- return ResultFactory.GetResult(playlistText, Common.Net.MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
+ return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
}
protected override string GetAudioArguments(StreamState state)
@@ -640,10 +641,19 @@ namespace MediaBrowser.Api.Playback.Hls
{
var codec = state.OutputVideoCodec;
+ var args = "-codec:v:0 " + codec;
+
+ if (state.EnableMpegtsM2TsMode)
+ {
+ args += " -mpegts_m2ts_mode 1";
+ }
+
// See if we can save come cpu cycles by avoiding encoding
- if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
{
- return state.VideoStream != null && IsH264(state.VideoStream) ? "-codec:v:0 copy -bsf:v h264_mp4toannexb" : "-codec:v:0 copy";
+ return state.VideoStream != null && IsH264(state.VideoStream) ?
+ args + " -bsf:v h264_mp4toannexb" :
+ args;
}
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
@@ -651,7 +661,7 @@ namespace MediaBrowser.Api.Playback.Hls
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
- var args = "-codec:v:0 " + codec + " " + GetVideoQualityParam(state, "libx264", true) + keyFrameArg;
+ args += " " + GetVideoQualityParam(state, H264Encoder, true) + keyFrameArg;
// Add resolution params, if specified
if (!hasGraphicalSubs)
@@ -677,7 +687,7 @@ namespace MediaBrowser.Api.Playback.Hls
// If isEncoding is true we're actually starting ffmpeg
var startNumberParam = isEncoding ? GetStartNumber(state).ToString(UsCulture) : "0";
- var args = string.Format("{0} -i {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"",
+ var args = string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"",
inputModifier,
GetInputArgument(transcodingJobId, state),
threads,
diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
index 2263a2b37..14045b3a5 100644
--- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
+++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
@@ -1,4 +1,5 @@
using MediaBrowser.Controller;
+using MediaBrowser.Model.Dlna;
using ServiceStack;
using System;
using System.IO;
@@ -65,7 +66,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
var file = request.PlaylistId + Path.GetExtension(Request.PathInfo);
- file = Path.Combine(_appPaths.TranscodingTempPath, file);
+ file = Path.Combine(_appPaths.TranscodingTempPath, EncodingContext.Streaming.ToString().ToLower(), file);
return ResultFactory.GetStaticFileResult(Request, file, FileShare.ReadWrite);
}
@@ -84,7 +85,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
- file = Path.Combine(_appPaths.TranscodingTempPath, file);
+ file = Path.Combine(_appPaths.TranscodingTempPath, EncodingContext.Streaming.ToString().ToLower(), file);
return ResultFactory.GetStaticFileResult(Request, file, FileShare.ReadWrite);
}
diff --git a/MediaBrowser.Api/Playback/Hls/MpegDashService.cs b/MediaBrowser.Api/Playback/Hls/MpegDashService.cs
index 260a4c467..87e2eedcf 100644
--- a/MediaBrowser.Api/Playback/Hls/MpegDashService.cs
+++ b/MediaBrowser.Api/Playback/Hls/MpegDashService.cs
@@ -18,6 +18,7 @@ using System.Security;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
namespace MediaBrowser.Api.Playback.Hls
{
@@ -97,7 +98,7 @@ namespace MediaBrowser.Api.Playback.Hls
playlistText = GetManifestText(state);
}
- return ResultFactory.GetResult(playlistText, Common.Net.MimeTypes.GetMimeType("playlist.mpd"), new Dictionary<string, string>());
+ return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.mpd"), new Dictionary<string, string>());
}
private string GetManifestText(StreamState state)
@@ -583,10 +584,19 @@ namespace MediaBrowser.Api.Playback.Hls
{
var codec = state.OutputVideoCodec;
+ var args = "-codec:v:0 " + codec;
+
+ if (state.EnableMpegtsM2TsMode)
+ {
+ args += " -mpegts_m2ts_mode 1";
+ }
+
// See if we can save come cpu cycles by avoiding encoding
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
{
- return IsH264(state.VideoStream) ? "-codec:v:0 copy -bsf:v h264_mp4toannexb" : "-codec:v:0 copy";
+ return state.VideoStream != null && IsH264(state.VideoStream) ?
+ args + " -bsf:v h264_mp4toannexb" :
+ args;
}
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
@@ -594,7 +604,7 @@ namespace MediaBrowser.Api.Playback.Hls
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
- var args = "-codec:v:0 " + codec + " " + GetVideoQualityParam(state, "libx264", true) + keyFrameArg;
+ args+= " " + GetVideoQualityParam(state, H264Encoder, true) + keyFrameArg;
args += " -r 24 -g 24";
@@ -627,7 +637,7 @@ namespace MediaBrowser.Api.Playback.Hls
var segmentFilename = Path.GetFileNameWithoutExtension(outputPath) + "%03d" + GetSegmentFileExtension(state);
- var args = string.Format("{0} -i {1} -map_metadata -1 -threads {2} {3} {4} -copyts {5} -f ssegment -segment_time {6} -segment_list_size {8} -segment_list \"{9}\" {10}",
+ var args = string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts {5} -f ssegment -segment_time {6} -segment_list_size {8} -segment_list \"{9}\" {10}",
inputModifier,
GetInputArgument(transcodingJobId, state),
threads,
diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
index 06fa4065c..d786b51b3 100644
--- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
@@ -5,12 +5,11 @@ using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.IO;
using ServiceStack;
using System;
using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
namespace MediaBrowser.Api.Playback.Hls
{
@@ -72,7 +71,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
- file = Path.Combine(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, file);
+ file = Path.Combine(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, EncodingContext.Streaming.ToString().ToLower(), file);
return ResultFactory.GetStaticFileResult(Request, file);
}
@@ -136,18 +135,27 @@ namespace MediaBrowser.Api.Playback.Hls
{
var codec = state.OutputVideoCodec;
+ var args = "-codec:v:0 " + codec;
+
+ if (state.EnableMpegtsM2TsMode)
+ {
+ args += " -mpegts_m2ts_mode 1";
+ }
+
// See if we can save come cpu cycles by avoiding encoding
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
{
- return state.VideoStream != null && IsH264(state.VideoStream) ? "-codec:v:0 copy -bsf:v h264_mp4toannexb" : "-codec:v:0 copy";
+ return state.VideoStream != null && IsH264(state.VideoStream) ?
+ args + " -bsf:v h264_mp4toannexb" :
+ args;
}
-
+
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
state.SegmentLength.ToString(UsCulture));
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
- var args = "-codec:v:0 " + codec + " " + GetVideoQualityParam(state, "libx264", true) + keyFrameArg;
+ args += " " + GetVideoQualityParam(state, H264Encoder, true) + keyFrameArg;
// Add resolution params, if specified
if (!hasGraphicalSubs)
diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
index ae592c428..725526ecd 100644
--- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
@@ -82,7 +82,7 @@ namespace MediaBrowser.Api.Playback.Progressive
var inputModifier = GetInputModifier(state);
- return string.Format("{0} -i {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
+ return string.Format("{0} {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
inputModifier,
GetInputArgument(transcodingJobId, state),
threads,
diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
index a64866d68..5ef72a495 100644
--- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
@@ -103,7 +103,7 @@ namespace MediaBrowser.Api.Playback.Progressive
var inputModifier = GetInputModifier(state);
- return string.Format("{0} -i {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"",
+ return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"",
inputModifier,
GetInputArgument(transcodingJobId, state),
keyFrame,
@@ -124,7 +124,7 @@ namespace MediaBrowser.Api.Playback.Progressive
/// <returns>System.String.</returns>
private string GetVideoArguments(StreamState state, string codec)
{
- var args = "-vcodec " + codec;
+ var args = "-codec:v:0 " + codec;
if (state.EnableMpegtsM2TsMode)
{
@@ -134,7 +134,9 @@ namespace MediaBrowser.Api.Playback.Progressive
// See if we can save come cpu cycles by avoiding encoding
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
{
- return state.VideoStream != null && IsH264(state.VideoStream) ? args + " -bsf:v h264_mp4toannexb" : args;
+ return state.VideoStream != null && IsH264(state.VideoStream) && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) ?
+ args + " -bsf:v h264_mp4toannexb" :
+ args;
}
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
@@ -182,13 +184,13 @@ namespace MediaBrowser.Api.Playback.Progressive
// Get the output codec name
var codec = state.OutputAudioCodec;
+ var args = "-codec:a:0 " + codec;
+
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
{
- return "-acodec copy";
+ return args;
}
- var args = "-acodec " + codec;
-
// Add the number of audio channels
var channels = state.OutputAudioChannels;
diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs
index d26259a3a..40e765f1a 100644
--- a/MediaBrowser.Api/Playback/StreamState.cs
+++ b/MediaBrowser.Api/Playback/StreamState.cs
@@ -11,6 +11,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading;
+using MediaBrowser.Model.Net;
namespace MediaBrowser.Api.Playback
{
diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs
index f34c53b16..cb8f91ab6 100644
--- a/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs
+++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs
@@ -1,5 +1,6 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Common.ScheduledTasks;
+using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Tasks;
diff --git a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs
index e6b525e53..25130776e 100644
--- a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs
+++ b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs
@@ -1,5 +1,6 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Session;
diff --git a/MediaBrowser.Api/Session/SessionsService.cs b/MediaBrowser.Api/Session/SessionsService.cs
index d2881893b..59b8e85c2 100644
--- a/MediaBrowser.Api/Session/SessionsService.cs
+++ b/MediaBrowser.Api/Session/SessionsService.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Devices;
+using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session;
@@ -299,6 +300,7 @@ namespace MediaBrowser.Api.Session
private readonly IUserManager _userManager;
private readonly IAuthorizationContext _authContext;
private readonly IAuthenticationRepository _authRepo;
+ private readonly IDeviceManager _deviceManager;
/// <summary>
/// Initializes a new instance of the <see cref="SessionsService" /> class.
@@ -307,12 +309,13 @@ namespace MediaBrowser.Api.Session
/// <param name="userManager">The user manager.</param>
/// <param name="authContext">The authentication context.</param>
/// <param name="authRepo">The authentication repo.</param>
- public SessionsService(ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext, IAuthenticationRepository authRepo)
+ public SessionsService(ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext, IAuthenticationRepository authRepo, IDeviceManager deviceManager)
{
_sessionManager = sessionManager;
_userManager = userManager;
_authContext = authContext;
_authRepo = authRepo;
+ _deviceManager = deviceManager;
}
public void Delete(RevokeKey request)
@@ -373,15 +376,30 @@ namespace MediaBrowser.Api.Session
var user = _userManager.GetUserById(request.ControllableByUserId.Value);
- if (!user.Configuration.EnableRemoteControlOfOtherUsers)
+ if (!user.Policy.EnableRemoteControlOfOtherUsers)
{
result = result.Where(i => i.ContainsUser(request.ControllableByUserId.Value));
}
- if (!user.Configuration.EnableSharedDeviceControl)
+ if (!user.Policy.EnableSharedDeviceControl)
{
result = result.Where(i => !i.UserId.HasValue);
}
+
+ result = result.Where(i =>
+ {
+ var deviceId = i.DeviceId;
+
+ if (!string.IsNullOrWhiteSpace(deviceId))
+ {
+ if (!_deviceManager.CanAccessDevice(user.Id.ToString("N"), deviceId))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ });
}
return ToOptimizedResult(result.Select(_sessionManager.GetSessionInfoDto).ToList());
diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs
index a2a93d50e..32e6ba076 100644
--- a/MediaBrowser.Api/Subtitles/SubtitleService.cs
+++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs
@@ -15,6 +15,7 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
namespace MediaBrowser.Api.Subtitles
{
@@ -175,7 +176,7 @@ namespace MediaBrowser.Api.Subtitles
builder.AppendLine("#EXT-X-ENDLIST");
- return ResultFactory.GetResult(builder.ToString(), Common.Net.MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
+ return ResultFactory.GetResult(builder.ToString(), MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
}
public object Get(GetSubtitle request)
@@ -199,7 +200,7 @@ namespace MediaBrowser.Api.Subtitles
var stream = GetSubtitles(request).Result;
- return ResultFactory.GetResult(stream, Common.Net.MimeTypes.GetMimeType("file." + request.Format));
+ return ResultFactory.GetResult(stream, MimeTypes.GetMimeType("file." + request.Format));
}
private async Task<Stream> GetSubtitles(GetSubtitle request)
@@ -240,7 +241,7 @@ namespace MediaBrowser.Api.Subtitles
{
var result = _subtitleManager.GetRemoteSubtitles(request.Id, CancellationToken.None).Result;
- return ResultFactory.GetResult(result.Stream, Common.Net.MimeTypes.GetMimeType("file." + result.Format));
+ return ResultFactory.GetResult(result.Stream, MimeTypes.GetMimeType("file." + result.Format));
}
public void Post(DownloadRemoteSubtitles request)
diff --git a/MediaBrowser.Api/Sync/SyncService.cs b/MediaBrowser.Api/Sync/SyncService.cs
index cefb0e46e..a7467c12f 100644
--- a/MediaBrowser.Api/Sync/SyncService.cs
+++ b/MediaBrowser.Api/Sync/SyncService.cs
@@ -5,6 +5,7 @@ using MediaBrowser.Controller.Sync;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Sync;
+using MediaBrowser.Model.Users;
using ServiceStack;
using System;
using System.Collections.Generic;
@@ -27,6 +28,11 @@ namespace MediaBrowser.Api.Sync
public string Id { get; set; }
}
+ [Route("/Sync/Jobs/{Id}", "POST", Summary = "Updates a sync job.")]
+ public class UpdateSyncJob : SyncJob, IReturnVoid
+ {
+ }
+
[Route("/Sync/JobItems", "GET", Summary = "Gets sync job items.")]
public class GetSyncJobItems : SyncJobItemQuery, IReturn<QueryResult<SyncJobItem>>
{
@@ -55,8 +61,14 @@ namespace MediaBrowser.Api.Sync
[ApiMember(Name = "UserId", Description = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
public string UserId { get; set; }
- [ApiMember(Name = "ItemIds", Description = "ItemIds", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+ [ApiMember(Name = "ItemIds", Description = "ItemIds", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string ItemIds { get; set; }
+
+ [ApiMember(Name = "ParentId", Description = "ParentId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string ParentId { get; set; }
+
+ [ApiMember(Name = "Category", Description = "Category", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public SyncCategory? Category { get; set; }
}
[Route("/Sync/JobItems/{Id}/Transferred", "POST", Summary = "Reports that a sync job item has successfully been transferred.")]
@@ -73,6 +85,23 @@ namespace MediaBrowser.Api.Sync
public string Id { get; set; }
}
+ [Route("/Sync/OfflineActions", "POST", Summary = "Reports an action that occurred while offline.")]
+ public class ReportOfflineActions : List<UserAction>, IReturnVoid
+ {
+ }
+
+ [Route("/Sync/Items/Ready", "GET", Summary = "Gets ready to download sync items.")]
+ public class GetReadySyncItems : IReturn<List<SyncedItem>>
+ {
+ [ApiMember(Name = "TargetId", Description = "TargetId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string TargetId { get; set; }
+ }
+
+ [Route("/Sync/Data", "POST", Summary = "Syncs data between device and server")]
+ public class SyncData : SyncDataRequest, IReturn<SyncDataResponse>
+ {
+ }
+
[Authenticated]
public class SyncService : BaseApiService
{
@@ -94,9 +123,9 @@ namespace MediaBrowser.Api.Sync
return ToOptimizedResult(result);
}
- public object Get(GetSyncJobs request)
+ public async Task<object> Get(GetSyncJobs request)
{
- var result = _syncManager.GetJobs(request);
+ var result = await _syncManager.GetJobs(request).ConfigureAwait(false);
return ToOptimizedResult(result);
}
@@ -155,21 +184,64 @@ namespace MediaBrowser.Api.Sync
result.Targets = _syncManager.GetSyncTargets(request.UserId)
.ToList();
- var dtos = request.ItemIds.Split(',')
- .Select(_libraryManager.GetItemById)
- .Where(i => i != null)
- .Select(i => _dtoService.GetBaseItemDto(i, new DtoOptions
+ if (request.Category.HasValue)
+ {
+ result.Options = SyncHelper.GetSyncOptions(request.Category.Value);
+ }
+ else
+ {
+ var dtoOptions = new DtoOptions
{
Fields = new List<ItemFields>
{
ItemFields.SyncInfo
}
- }))
- .ToList();
+ };
- result.Options = SyncHelper.GetSyncOptions(dtos);
+ var dtos = request.ItemIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(_libraryManager.GetItemById)
+ .Where(i => i != null)
+ .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions))
+ .ToList();
+
+ result.Options = SyncHelper.GetSyncOptions(dtos);
+ }
return ToOptimizedResult(result);
}
+
+ public void Post(ReportOfflineActions request)
+ {
+ var task = PostAsync(request);
+
+ Task.WaitAll(task);
+ }
+
+ public async Task PostAsync(ReportOfflineActions request)
+ {
+ foreach (var action in request)
+ {
+ await _syncManager.ReportOfflineAction(action).ConfigureAwait(false);
+ }
+ }
+
+ public object Get(GetReadySyncItems request)
+ {
+ return ToOptimizedResult(_syncManager.GetReadySyncItems(request.TargetId));
+ }
+
+ public async Task<object> Post(SyncData request)
+ {
+ var response = await _syncManager.SyncData(request).ConfigureAwait(false);
+
+ return ToOptimizedResult(response);
+ }
+
+ public void Post(UpdateSyncJob request)
+ {
+ var task = _syncManager.UpdateJob(request);
+
+ Task.WaitAll(task);
+ }
}
}
diff --git a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs
index 4629b2a8c..a951cd3d6 100644
--- a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs
+++ b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs
@@ -1,5 +1,5 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Activity;
+using MediaBrowser.Controller.Activity;
+using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Logging;
diff --git a/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs b/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs
index c20cef3b3..49a3e3291 100644
--- a/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs
+++ b/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs
@@ -1,5 +1,6 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
+using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.System;
using System.Threading.Tasks;
diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs
index 2299b2b1a..128423238 100644
--- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs
+++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs
@@ -83,17 +83,16 @@ namespace MediaBrowser.Api.UserLibrary
{
var item = GetArtist(request.Name, LibraryManager);
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true));
+ var dtoOptions = new DtoOptions();
if (request.UserId.HasValue)
{
var user = UserManager.GetUserById(request.UserId.Value);
-
- return DtoService.GetBaseItemDto(item, fields.ToList(), user);
+
+ return DtoService.GetBaseItemDto(item, dtoOptions, user);
}
- return DtoService.GetBaseItemDto(item, fields.ToList());
+ return DtoService.GetBaseItemDto(item, dtoOptions);
}
/// <summary>
diff --git a/MediaBrowser.Api/UserLibrary/GameGenresService.cs b/MediaBrowser.Api/UserLibrary/GameGenresService.cs
index adc21c362..2f1c73ace 100644
--- a/MediaBrowser.Api/UserLibrary/GameGenresService.cs
+++ b/MediaBrowser.Api/UserLibrary/GameGenresService.cs
@@ -69,17 +69,16 @@ namespace MediaBrowser.Api.UserLibrary
{
var item = GetGameGenre(request.Name, LibraryManager);
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true));
+ var dtoOptions = new DtoOptions();
if (request.UserId.HasValue)
{
var user = UserManager.GetUserById(request.UserId.Value);
- return DtoService.GetBaseItemDto(item, fields.ToList(), user);
+ return DtoService.GetBaseItemDto(item, dtoOptions, user);
}
- return DtoService.GetBaseItemDto(item, fields.ToList());
+ return DtoService.GetBaseItemDto(item, dtoOptions);
}
/// <summary>
diff --git a/MediaBrowser.Api/UserLibrary/GenresService.cs b/MediaBrowser.Api/UserLibrary/GenresService.cs
index 7b6b18c9c..db0b0fe61 100644
--- a/MediaBrowser.Api/UserLibrary/GenresService.cs
+++ b/MediaBrowser.Api/UserLibrary/GenresService.cs
@@ -74,17 +74,16 @@ namespace MediaBrowser.Api.UserLibrary
{
var item = GetGenre(request.Name, LibraryManager);
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true));
+ var dtoOptions = new DtoOptions();
if (request.UserId.HasValue)
{
var user = UserManager.GetUserById(request.UserId.Value);
- return DtoService.GetBaseItemDto(item, fields.ToList(), user);
+ return DtoService.GetBaseItemDto(item, dtoOptions, user);
}
- return DtoService.GetBaseItemDto(item, fields.ToList());
+ return DtoService.GetBaseItemDto(item, dtoOptions);
}
/// <summary>
diff --git a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs
index 1088ada02..f8575aa7c 100644
--- a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs
+++ b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs
@@ -69,17 +69,16 @@ namespace MediaBrowser.Api.UserLibrary
{
var item = GetMusicGenre(request.Name, LibraryManager);
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true));
+ var dtoOptions = new DtoOptions();
if (request.UserId.HasValue)
{
var user = UserManager.GetUserById(request.UserId.Value);
- return DtoService.GetBaseItemDto(item, fields.ToList(), user);
+ return DtoService.GetBaseItemDto(item, dtoOptions, user);
}
- return DtoService.GetBaseItemDto(item, fields.ToList());
+ return DtoService.GetBaseItemDto(item, dtoOptions);
}
/// <summary>
diff --git a/MediaBrowser.Api/UserLibrary/PersonsService.cs b/MediaBrowser.Api/UserLibrary/PersonsService.cs
index dd80801ee..33ce6cd80 100644
--- a/MediaBrowser.Api/UserLibrary/PersonsService.cs
+++ b/MediaBrowser.Api/UserLibrary/PersonsService.cs
@@ -86,17 +86,16 @@ namespace MediaBrowser.Api.UserLibrary
{
var item = GetPerson(request.Name, LibraryManager);
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true));
+ var dtoOptions = new DtoOptions();
if (request.UserId.HasValue)
{
var user = UserManager.GetUserById(request.UserId.Value);
- return DtoService.GetBaseItemDto(item, fields.ToList(), user);
+ return DtoService.GetBaseItemDto(item, dtoOptions, user);
}
- return DtoService.GetBaseItemDto(item, fields.ToList());
+ return DtoService.GetBaseItemDto(item, dtoOptions);
}
/// <summary>
diff --git a/MediaBrowser.Api/UserLibrary/StudiosService.cs b/MediaBrowser.Api/UserLibrary/StudiosService.cs
index 2024d779b..272134b70 100644
--- a/MediaBrowser.Api/UserLibrary/StudiosService.cs
+++ b/MediaBrowser.Api/UserLibrary/StudiosService.cs
@@ -73,17 +73,16 @@ namespace MediaBrowser.Api.UserLibrary
{
var item = GetStudio(request.Name, LibraryManager);
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true));
+ var dtoOptions = new DtoOptions();
if (request.UserId.HasValue)
{
var user = UserManager.GetUserById(request.UserId.Value);
- return DtoService.GetBaseItemDto(item, fields.ToList(), user);
+ return DtoService.GetBaseItemDto(item, dtoOptions, user);
}
- return DtoService.GetBaseItemDto(item, fields.ToList());
+ return DtoService.GetBaseItemDto(item, dtoOptions);
}
/// <summary>
diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs
index 040cad436..45a330a50 100644
--- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs
+++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs
@@ -407,9 +407,6 @@ namespace MediaBrowser.Api.UserLibrary
{
var user = _userManager.GetUserById(request.UserId);
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
-
var query = new UserViewQuery
{
UserId = request.UserId
@@ -423,7 +420,9 @@ namespace MediaBrowser.Api.UserLibrary
var folders = await _userViewManager.GetUserViews(query, CancellationToken.None).ConfigureAwait(false);
- var dtos = folders.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
+ var dtoOptions = new DtoOptions();
+
+ var dtos = folders.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user))
.ToArray();
var result = new QueryResult<BaseItemDto>
@@ -443,14 +442,16 @@ namespace MediaBrowser.Api.UserLibrary
user.RootFolder :
_libraryManager.GetItemById(request.Id);
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
-
var series = item as Series;
// Get them from the child tree
if (series != null)
{
+ var dtoOptions = new DtoOptions();
+
+ // Avoid implicitly captured closure
+ var currentUser = user;
+
var dtos = series
.GetRecursiveChildren()
.Where(i => i is Episode && i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == 0)
@@ -468,7 +469,7 @@ namespace MediaBrowser.Api.UserLibrary
return DateTime.MinValue;
})
.ThenBy(i => i.SortName)
- .Select(i => _dtoService.GetBaseItemDto(i, fields, user));
+ .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, currentUser));
return dtos.ToList();
}
@@ -478,10 +479,12 @@ namespace MediaBrowser.Api.UserLibrary
// Get them from the db
if (movie != null)
{
+ var dtoOptions = new DtoOptions();
+
var dtos = movie.SpecialFeatureIds
.Select(_libraryManager.GetItemById)
.OrderBy(i => i.SortName)
- .Select(i => _dtoService.GetBaseItemDto(i, fields, user, item));
+ .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
return dtos.ToList();
}
@@ -507,9 +510,6 @@ namespace MediaBrowser.Api.UserLibrary
var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _libraryManager.GetItemById(request.Id);
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
-
var trailerIds = new List<Guid>();
var hasTrailers = item as IHasTrailers;
@@ -518,10 +518,12 @@ namespace MediaBrowser.Api.UserLibrary
trailerIds = hasTrailers.GetTrailerIds();
}
+ var dtoOptions = new DtoOptions();
+
var dtos = trailerIds
.Select(_libraryManager.GetItemById)
.OrderBy(i => i.SortName)
- .Select(i => _dtoService.GetBaseItemDto(i, fields, user, item));
+ .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
return dtos.ToList();
}
@@ -537,10 +539,9 @@ namespace MediaBrowser.Api.UserLibrary
var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _libraryManager.GetItemById(request.Id);
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
+ var dtoOptions = new DtoOptions();
- var result = _dtoService.GetBaseItemDto(item, fields, user);
+ var result = _dtoService.GetBaseItemDto(item, dtoOptions, user);
return ToOptimizedSerializedResultUsingCache(result);
}
@@ -556,10 +557,9 @@ namespace MediaBrowser.Api.UserLibrary
var item = user.RootFolder;
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
+ var dtoOptions = new DtoOptions();
- var result = _dtoService.GetBaseItemDto(item, fields, user);
+ var result = _dtoService.GetBaseItemDto(item, dtoOptions, user);
return ToOptimizedSerializedResultUsingCache(result);
}
@@ -577,12 +577,9 @@ namespace MediaBrowser.Api.UserLibrary
var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false);
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields))
- .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
- .ToList();
+ var dtoOptions = new DtoOptions();
- var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
+ var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user))
.ToArray();
var result = new ItemsResult
diff --git a/MediaBrowser.Api/UserLibrary/YearsService.cs b/MediaBrowser.Api/UserLibrary/YearsService.cs
index 2b300f900..b1b0aeb63 100644
--- a/MediaBrowser.Api/UserLibrary/YearsService.cs
+++ b/MediaBrowser.Api/UserLibrary/YearsService.cs
@@ -73,17 +73,16 @@ namespace MediaBrowser.Api.UserLibrary
{
var item = LibraryManager.GetYear(request.Year);
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true));
+ var dtoOptions = new DtoOptions();
if (request.UserId.HasValue)
{
var user = UserManager.GetUserById(request.UserId.Value);
- return DtoService.GetBaseItemDto(item, fields.ToList(), user);
+ return DtoService.GetBaseItemDto(item, dtoOptions, user);
}
- return DtoService.GetBaseItemDto(item, fields.ToList());
+ return DtoService.GetBaseItemDto(item, dtoOptions);
}
/// <summary>
diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs
index 4b720c775..51a7584b8 100644
--- a/MediaBrowser.Api/UserService.cs
+++ b/MediaBrowser.Api/UserService.cs
@@ -1,10 +1,12 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Connect;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Users;
@@ -51,7 +53,7 @@ namespace MediaBrowser.Api
/// </summary>
/// <value>The id.</value>
[ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public Guid Id { get; set; }
+ public string Id { get; set; }
}
/// <summary>
@@ -66,7 +68,7 @@ namespace MediaBrowser.Api
/// </summary>
/// <value>The id.</value>
[ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public Guid Id { get; set; }
+ public string Id { get; set; }
}
/// <summary>
@@ -80,7 +82,7 @@ namespace MediaBrowser.Api
/// </summary>
/// <value>The id.</value>
[ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public Guid Id { get; set; }
+ public string Id { get; set; }
/// <summary>
/// Gets or sets the password.
@@ -125,7 +127,7 @@ namespace MediaBrowser.Api
/// Gets or sets the id.
/// </summary>
/// <value>The id.</value>
- public Guid Id { get; set; }
+ public string Id { get; set; }
/// <summary>
/// Gets or sets the password.
@@ -156,6 +158,28 @@ namespace MediaBrowser.Api
}
/// <summary>
+ /// Class UpdateUser
+ /// </summary>
+ [Route("/Users/{Id}/Policy", "POST", Summary = "Updates a user policy")]
+ [Authenticated(Roles = "admin")]
+ public class UpdateUserPolicy : UserPolicy, IReturnVoid
+ {
+ [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+ public string Id { get; set; }
+ }
+
+ /// <summary>
+ /// Class UpdateUser
+ /// </summary>
+ [Route("/Users/{Id}/Configuration", "POST", Summary = "Updates a user configuration")]
+ [Authenticated]
+ public class UpdateUserConfiguration : UserConfiguration, IReturnVoid
+ {
+ [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+ public string Id { get; set; }
+ }
+
+ /// <summary>
/// Class CreateUser
/// </summary>
[Route("/Users/New", "POST", Summary = "Creates a user")]
@@ -193,22 +217,18 @@ namespace MediaBrowser.Api
private readonly ISessionManager _sessionMananger;
private readonly IServerConfigurationManager _config;
private readonly INetworkManager _networkManager;
+ private readonly IDeviceManager _deviceManager;
public IAuthorizationContext AuthorizationContext { get; set; }
- /// <summary>
- /// Initializes a new instance of the <see cref="UserService" /> class.
- /// </summary>
- /// <param name="userManager">The user manager.</param>
- /// <param name="dtoService">The dto service.</param>
- /// <param name="sessionMananger">The session mananger.</param>
- public UserService(IUserManager userManager, IDtoService dtoService, ISessionManager sessionMananger, IServerConfigurationManager config, INetworkManager networkManager)
+ public UserService(IUserManager userManager, IDtoService dtoService, ISessionManager sessionMananger, IServerConfigurationManager config, INetworkManager networkManager, IDeviceManager deviceManager)
{
_userManager = userManager;
_dtoService = dtoService;
_sessionMananger = sessionMananger;
_config = config;
_networkManager = networkManager;
+ _deviceManager = deviceManager;
}
public object Get(GetPublicUsers request)
@@ -222,18 +242,12 @@ namespace MediaBrowser.Api
});
}
- // TODO: Uncomment once clients can handle an empty user list (and below)
- //if (Request.IsLocal || IsInLocalNetwork(Request.RemoteIp))
+ return Get(new GetUsers
{
- return Get(new GetUsers
- {
- IsHidden = false,
- IsDisabled = false
- });
- }
+ IsHidden = false,
+ IsDisabled = false
- //// Return empty when external
- //return ToOptimizedResult(new List<UserDto>());
+ }, true);
}
/// <summary>
@@ -243,24 +257,38 @@ namespace MediaBrowser.Api
/// <returns>System.Object.</returns>
public object Get(GetUsers request)
{
+ return Get(request, false);
+ }
+
+ private object Get(GetUsers request, bool filterByDevice)
+ {
var users = _userManager.Users;
if (request.IsDisabled.HasValue)
{
- users = users.Where(i => i.Configuration.IsDisabled == request.IsDisabled.Value);
+ users = users.Where(i => i.Policy.IsDisabled == request.IsDisabled.Value);
}
if (request.IsHidden.HasValue)
{
- users = users.Where(i => i.Configuration.IsHidden == request.IsHidden.Value);
+ users = users.Where(i => i.Policy.IsHidden == request.IsHidden.Value);
}
if (request.IsGuest.HasValue)
{
-
users = users.Where(i => (i.ConnectLinkType.HasValue && i.ConnectLinkType.Value == UserLinkType.Guest) == request.IsGuest.Value);
}
+ if (filterByDevice)
+ {
+ var deviceId = AuthorizationContext.GetAuthorizationInfo(Request).DeviceId;
+
+ if (!string.IsNullOrWhiteSpace(deviceId))
+ {
+ users = users.Where(i => _deviceManager.CanAccessDevice(i.Id.ToString("N"), deviceId));
+ }
+ }
+
var result = users
.OrderBy(u => u.Name)
.Select(i => _userManager.GetUserDto(i, Request.RemoteIp))
@@ -428,39 +456,13 @@ namespace MediaBrowser.Api
var user = _userManager.GetUserById(id);
- // If removing admin access
- if (!dtoUser.Configuration.IsAdministrator && user.Configuration.IsAdministrator)
- {
- if (_userManager.Users.Count(i => i.Configuration.IsAdministrator) == 1)
- {
- throw new ArgumentException("There must be at least one user in the system with administrative access.");
- }
- }
-
- // If disabling
- if (dtoUser.Configuration.IsDisabled && user.Configuration.IsAdministrator)
- {
- throw new ArgumentException("Administrators cannot be disabled.");
- }
-
- // If disabling
- if (dtoUser.Configuration.IsDisabled && !user.Configuration.IsDisabled)
- {
- if (_userManager.Users.Count(i => !i.Configuration.IsDisabled) == 1)
- {
- throw new ArgumentException("There must be at least one enabled user in the system.");
- }
-
- await _sessionMananger.RevokeUserTokens(user.Id.ToString("N")).ConfigureAwait(false);
- }
-
var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ?
_userManager.UpdateUser(user) :
_userManager.RenameUser(user, dtoUser.Name);
await task.ConfigureAwait(false);
- user.UpdateConfiguration(dtoUser.Configuration);
+ await _userManager.UpdateConfiguration(dtoUser.Id, dtoUser.Configuration);
}
/// <summary>
@@ -495,5 +497,51 @@ namespace MediaBrowser.Api
{
return _userManager.RedeemPasswordResetPin(request.Pin);
}
+
+ public void Post(UpdateUserConfiguration request)
+ {
+ var task = _userManager.UpdateConfiguration(request.Id, request);
+
+ Task.WaitAll(task);
+ }
+
+ public void Post(UpdateUserPolicy request)
+ {
+ var task = UpdateUserPolicy(request);
+ Task.WaitAll(task);
+ }
+
+ private async Task UpdateUserPolicy(UpdateUserPolicy request)
+ {
+ var user = _userManager.GetUserById(request.Id);
+
+ // If removing admin access
+ if (!request.IsAdministrator && user.Policy.IsAdministrator)
+ {
+ if (_userManager.Users.Count(i => i.Policy.IsAdministrator) == 1)
+ {
+ throw new ArgumentException("There must be at least one user in the system with administrative access.");
+ }
+ }
+
+ // If disabling
+ if (request.IsDisabled && user.Policy.IsAdministrator)
+ {
+ throw new ArgumentException("Administrators cannot be disabled.");
+ }
+
+ // If disabling
+ if (request.IsDisabled && !user.Policy.IsDisabled)
+ {
+ if (_userManager.Users.Count(i => !i.Policy.IsDisabled) == 1)
+ {
+ throw new ArgumentException("There must be at least one enabled user in the system.");
+ }
+
+ await _sessionMananger.RevokeUserTokens(user.Id.ToString("N")).ConfigureAwait(false);
+ }
+
+ await _userManager.UpdateUserPolicy(request.Id, request).ConfigureAwait(false);
+ }
}
}
diff --git a/MediaBrowser.Api/VideosService.cs b/MediaBrowser.Api/VideosService.cs
index fbf9cec2e..28db46b98 100644
--- a/MediaBrowser.Api/VideosService.cs
+++ b/MediaBrowser.Api/VideosService.cs
@@ -5,6 +5,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying;
using ServiceStack;
using System;
@@ -79,15 +80,12 @@ namespace MediaBrowser.Api
: _libraryManager.RootFolder)
: _libraryManager.GetItemById(request.Id);
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields))
- .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
- .ToList();
+ var dtoOptions = new DtoOptions();
var video = (Video)item;
var items = video.GetAdditionalParts()
- .Select(i => _dtoService.GetBaseItemDto(i, fields, user, video))
+ .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, video))
.ToArray();
var result = new ItemsResult
@@ -114,11 +112,11 @@ namespace MediaBrowser.Api
{
link.PrimaryVersionId = null;
- await link.UpdateToRepository(ItemUpdateType.MetadataDownload, CancellationToken.None).ConfigureAwait(false);
+ await link.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
}
video.LinkedAlternateVersions.Clear();
- await video.UpdateToRepository(ItemUpdateType.MetadataDownload, CancellationToken.None).ConfigureAwait(false);
+ await video.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
}
public void Post(MergeVersions request)
@@ -186,7 +184,7 @@ namespace MediaBrowser.Api
{
item.PrimaryVersionId = primaryVersion.Id;
- await item.UpdateToRepository(ItemUpdateType.MetadataDownload, CancellationToken.None).ConfigureAwait(false);
+ await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
primaryVersion.LinkedAlternateVersions.Add(new LinkedChild
{
@@ -195,7 +193,7 @@ namespace MediaBrowser.Api
});
}
- await primaryVersion.UpdateToRepository(ItemUpdateType.MetadataDownload, CancellationToken.None).ConfigureAwait(false);
+ await primaryVersion.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
}
}
}
diff --git a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs
index 9e14d0ee8..4de1adcfa 100644
--- a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs
+++ b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs
@@ -466,7 +466,7 @@ namespace MediaBrowser.Common.Implementations
RegisterSingleInstance(FileSystemManager);
- HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, Logger, FileSystemManager, ConfigurationManager);
+ HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, Logger, FileSystemManager);
RegisterSingleInstance(HttpClient);
NetworkManager = CreateNetworkManager(LogManager.GetLogger("NetworkManager"));
diff --git a/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs b/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs
index 4a70697fa..c53947e44 100644
--- a/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs
+++ b/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs
@@ -105,7 +105,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
UpdateCachePath();
}
- public void AddParts(IEnumerable<IConfigurationFactory> factories)
+ public virtual void AddParts(IEnumerable<IConfigurationFactory> factories)
{
_configurationFactories = factories.ToArray();
@@ -208,20 +208,51 @@ namespace MediaBrowser.Common.Implementations.Configuration
lock (_configurationSyncLock)
{
- return ConfigurationHelper.GetXmlConfiguration(configurationType, file, XmlSerializer);
+ return LoadConfiguration(file, configurationType);
}
});
}
+ private object LoadConfiguration(string path, Type configurationType)
+ {
+ try
+ {
+ return XmlSerializer.DeserializeFromFile(configurationType, path);
+ }
+ catch (FileNotFoundException)
+ {
+ return Activator.CreateInstance(configurationType);
+ }
+ catch (DirectoryNotFoundException)
+ {
+ return Activator.CreateInstance(configurationType);
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error loading configuration file: {0}", ex, path);
+
+ return Activator.CreateInstance(configurationType);
+ }
+ }
+
public void SaveConfiguration(string key, object configuration)
{
- var configurationType = GetConfigurationType(key);
+ var configurationStore = GetConfigurationStore(key);
+ var configurationType = configurationStore.ConfigurationType;
if (configuration.GetType() != configurationType)
{
throw new ArgumentException("Expected configuration type is " + configurationType.Name);
}
+ var validatingStore = configurationStore as IValidatingConfiguration;
+ if (validatingStore != null)
+ {
+ var currentConfiguration = GetConfiguration(key);
+
+ validatingStore.Validate(currentConfiguration, configuration);
+ }
+
EventHelper.FireEventIfNotNull(NamedConfigurationUpdating, this, new ConfigurationUpdateEventArgs
{
Key = key,
@@ -239,6 +270,11 @@ namespace MediaBrowser.Common.Implementations.Configuration
XmlSerializer.SerializeToFile(configuration, path);
}
+ OnNamedConfigurationUpdated(key, configuration);
+ }
+
+ protected virtual void OnNamedConfigurationUpdated(string key, object configuration)
+ {
EventHelper.FireEventIfNotNull(NamedConfigurationUpdated, this, new ConfigurationUpdateEventArgs
{
Key = key,
@@ -249,9 +285,14 @@ namespace MediaBrowser.Common.Implementations.Configuration
public Type GetConfigurationType(string key)
{
- return _configurationStores
- .First(i => string.Equals(i.Key, key, StringComparison.OrdinalIgnoreCase))
+ return GetConfigurationStore(key)
.ConfigurationType;
}
+
+ private ConfigurationStore GetConfigurationStore(string key)
+ {
+ return _configurationStores
+ .First(i => string.Equals(i.Key, key, StringComparison.OrdinalIgnoreCase));
+ }
}
}
diff --git a/MediaBrowser.Common/Configuration/ConfigurationHelper.cs b/MediaBrowser.Common.Implementations/Configuration/ConfigurationHelper.cs
index 8c904b0db..ff5b8bd59 100644
--- a/MediaBrowser.Common/Configuration/ConfigurationHelper.cs
+++ b/MediaBrowser.Common.Implementations/Configuration/ConfigurationHelper.cs
@@ -3,7 +3,7 @@ using System;
using System.IO;
using System.Linq;
-namespace MediaBrowser.Common.Configuration
+namespace MediaBrowser.Common.Implementations.Configuration
{
/// <summary>
/// Class ConfigurationHelper
@@ -55,20 +55,5 @@ namespace MediaBrowser.Common.Configuration
return configuration;
}
}
-
- /// <summary>
- /// Reads an xml configuration file from the file system
- /// It will immediately save the configuration after loading it, just
- /// in case there are new serializable properties
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="path">The path.</param>
- /// <param name="xmlSerializer">The XML serializer.</param>
- /// <returns>``0.</returns>
- public static T GetXmlConfiguration<T>(string path, IXmlSerializer xmlSerializer)
- where T : class
- {
- return GetXmlConfiguration(typeof(T), path, xmlSerializer) as T;
- }
}
}
diff --git a/MediaBrowser.Common.Implementations/Devices/DeviceId.cs b/MediaBrowser.Common.Implementations/Devices/DeviceId.cs
index 5af236026..7c0dc1e1f 100644
--- a/MediaBrowser.Common.Implementations/Devices/DeviceId.cs
+++ b/MediaBrowser.Common.Implementations/Devices/DeviceId.cs
@@ -38,7 +38,10 @@ namespace MediaBrowser.Common.Implementations.Devices
_logger.Error("Invalid value found in device id file");
}
}
- catch (FileNotFoundException ex)
+ catch (DirectoryNotFoundException)
+ {
+ }
+ catch (FileNotFoundException)
{
}
catch (Exception ex)
diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
index 89d00b87d..1f82c5eb0 100644
--- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
+++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
@@ -41,7 +41,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
- private readonly IConfigurationManager _config;
/// <summary>
/// Initializes a new instance of the <see cref="HttpClientManager" /> class.
@@ -52,7 +51,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
/// <exception cref="System.ArgumentNullException">appPaths
/// or
/// logger</exception>
- public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem, IConfigurationManager config)
+ public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem)
{
if (appPaths == null)
{
@@ -65,7 +64,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
_logger = logger;
_fileSystem = fileSystem;
- _config = config;
_appPaths = appPaths;
// http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
diff --git a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
index 39fa9e802..ff08c31bc 100644
--- a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
+++ b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
@@ -81,6 +81,7 @@
<Compile Include="BaseApplicationHost.cs" />
<Compile Include="BaseApplicationPaths.cs" />
<Compile Include="Configuration\BaseConfigurationManager.cs" />
+ <Compile Include="Configuration\ConfigurationHelper.cs" />
<Compile Include="Devices\DeviceId.cs" />
<Compile Include="HttpClientManager\HttpClientInfo.cs" />
<Compile Include="HttpClientManager\HttpClientManager.cs" />
diff --git a/MediaBrowser.Common.Implementations/Security/MBLicenseFile.cs b/MediaBrowser.Common.Implementations/Security/MBLicenseFile.cs
index 8f3225f4e..63381efcd 100644
--- a/MediaBrowser.Common.Implementations/Security/MBLicenseFile.cs
+++ b/MediaBrowser.Common.Implementations/Security/MBLicenseFile.cs
@@ -101,6 +101,10 @@ namespace MediaBrowser.Common.Implementations.Security
{
contents = File.ReadAllLines(licenseFile);
}
+ catch (DirectoryNotFoundException)
+ {
+ (File.Create(licenseFile)).Close();
+ }
catch (FileNotFoundException)
{
(File.Create(licenseFile)).Close();
diff --git a/MediaBrowser.Common.Implementations/Serialization/XmlSerializer.cs b/MediaBrowser.Common.Implementations/Serialization/XmlSerializer.cs
index cef744753..04030522f 100644
--- a/MediaBrowser.Common.Implementations/Serialization/XmlSerializer.cs
+++ b/MediaBrowser.Common.Implementations/Serialization/XmlSerializer.cs
@@ -1,5 +1,6 @@
using MediaBrowser.Model.Serialization;
using System;
+using System.Collections.Concurrent;
using System.IO;
using System.Xml;
@@ -10,6 +11,17 @@ namespace MediaBrowser.Common.Implementations.Serialization
/// </summary>
public class XmlSerializer : IXmlSerializer
{
+ // Need to cache these
+ // http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html
+ private readonly ConcurrentDictionary<string, System.Xml.Serialization.XmlSerializer> _serializers =
+ new ConcurrentDictionary<string, System.Xml.Serialization.XmlSerializer>();
+
+ private System.Xml.Serialization.XmlSerializer GetSerializer(Type type)
+ {
+ var key = type.FullName;
+ return _serializers.GetOrAdd(key, k => new System.Xml.Serialization.XmlSerializer(type));
+ }
+
/// <summary>
/// Serializes to writer.
/// </summary>
@@ -18,7 +30,7 @@ namespace MediaBrowser.Common.Implementations.Serialization
private void SerializeToWriter(object obj, XmlTextWriter writer)
{
writer.Formatting = Formatting.Indented;
- var netSerializer = new System.Xml.Serialization.XmlSerializer(obj.GetType());
+ var netSerializer = GetSerializer(obj.GetType());
netSerializer.Serialize(writer, obj);
}
@@ -32,8 +44,7 @@ namespace MediaBrowser.Common.Implementations.Serialization
{
using (var reader = new XmlTextReader(stream))
{
- var netSerializer = new System.Xml.Serialization.XmlSerializer(type);
-
+ var netSerializer = GetSerializer(type);
return netSerializer.Deserialize(reader);
}
}
diff --git a/MediaBrowser.Common.Implementations/packages.config b/MediaBrowser.Common.Implementations/packages.config
index c57aea0b5..63d288bbb 100644
--- a/MediaBrowser.Common.Implementations/packages.config
+++ b/MediaBrowser.Common.Implementations/packages.config
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NLog" version="3.1.0.0" targetFramework="net45" />
- <package id="SharpCompress" version="0.10.2" targetFramework="net45" />
<package id="SimpleInjector" version="2.6.1" targetFramework="net45" />
</packages>
diff --git a/MediaBrowser.Common/Configuration/IConfigurationFactory.cs b/MediaBrowser.Common/Configuration/IConfigurationFactory.cs
index d418d0a42..6ed638536 100644
--- a/MediaBrowser.Common/Configuration/IConfigurationFactory.cs
+++ b/MediaBrowser.Common/Configuration/IConfigurationFactory.cs
@@ -14,4 +14,9 @@ namespace MediaBrowser.Common.Configuration
public Type ConfigurationType { get; set; }
}
+
+ public interface IValidatingConfiguration
+ {
+ void Validate(object oldConfig, object newConfig);
+ }
}
diff --git a/MediaBrowser.Common/Extensions/BaseExtensions.cs b/MediaBrowser.Common/Extensions/BaseExtensions.cs
index 8e96373f4..4c94f3aa2 100644
--- a/MediaBrowser.Common/Extensions/BaseExtensions.cs
+++ b/MediaBrowser.Common/Extensions/BaseExtensions.cs
@@ -1,4 +1,6 @@
using System;
+using System.Globalization;
+using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
@@ -54,6 +56,15 @@ namespace MediaBrowser.Common.Extensions
return sb.ToString();
}
+ public static string RemoveDiacritics(this string text)
+ {
+ return String.Concat(
+ text.Normalize(NormalizationForm.FormD)
+ .Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch) !=
+ UnicodeCategory.NonSpacingMark)
+ ).Normalize(NormalizationForm.FormC);
+ }
+
/// <summary>
/// Gets the M d5.
/// </summary>
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index 9fdfccaaf..c46dd4a4f 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -53,7 +53,6 @@
<Compile Include="..\SharedVersion.cs">
<Link>Properties\SharedVersion.cs</Link>
</Compile>
- <Compile Include="Configuration\ConfigurationHelper.cs" />
<Compile Include="Configuration\ConfigurationUpdateEventArgs.cs" />
<Compile Include="Configuration\IConfigurationManager.cs" />
<Compile Include="Configuration\IConfigurationFactory.cs" />
@@ -64,19 +63,12 @@
<Compile Include="IO\IFileSystem.cs" />
<Compile Include="IO\ProgressStream.cs" />
<Compile Include="IO\StreamDefaults.cs" />
- <Compile Include="Net\BasePeriodicWebSocketListener.cs" />
<Compile Include="Configuration\IApplicationPaths.cs" />
<Compile Include="Net\HttpRequestOptions.cs" />
<Compile Include="Net\HttpResponseInfo.cs" />
- <Compile Include="Net\IWebSocketListener.cs" />
<Compile Include="IApplicationHost.cs" />
<Compile Include="Net\IHttpClient.cs" />
<Compile Include="Net\INetworkManager.cs" />
- <Compile Include="Net\IWebSocket.cs" />
- <Compile Include="Net\IWebSocketConnection.cs" />
- <Compile Include="Net\MimeTypes.cs" />
- <Compile Include="Net\WebSocketConnectEventArgs.cs" />
- <Compile Include="Net\WebSocketMessageInfo.cs" />
<Compile Include="Plugins\IDependencyModule.cs" />
<Compile Include="Plugins\IPlugin.cs" />
<Compile Include="Progress\ActionableProgress.cs" />
diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs
index 6bbd69f04..9d0133c67 100644
--- a/MediaBrowser.Common/Plugins/BasePlugin.cs
+++ b/MediaBrowser.Common/Plugins/BasePlugin.cs
@@ -5,7 +5,6 @@ using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
-using System.Threading;
namespace MediaBrowser.Common.Plugins
{
@@ -164,11 +163,7 @@ namespace MediaBrowser.Common.Plugins
/// <summary>
/// The _configuration sync lock
/// </summary>
- private object _configurationSyncLock = new object();
- /// <summary>
- /// The _configuration initialized
- /// </summary>
- private bool _configurationInitialized;
+ private readonly object _configurationSyncLock = new object();
/// <summary>
/// The _configuration
/// </summary>
@@ -182,17 +177,43 @@ namespace MediaBrowser.Common.Plugins
get
{
// Lazy load
- LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationInitialized, ref _configurationSyncLock, () => ConfigurationHelper.GetXmlConfiguration(ConfigurationType, ConfigurationFilePath, XmlSerializer) as TConfigurationType);
+ if (_configuration == null)
+ {
+ lock (_configurationSyncLock)
+ {
+ if (_configuration == null)
+ {
+ _configuration = LoadConfiguration();
+ }
+ }
+ }
return _configuration;
}
protected set
{
_configuration = value;
+ }
+ }
- if (value == null)
- {
- _configurationInitialized = false;
- }
+ private TConfigurationType LoadConfiguration()
+ {
+ var path = ConfigurationFilePath;
+
+ try
+ {
+ return (TConfigurationType)XmlSerializer.DeserializeFromFile(typeof(TConfigurationType), path);
+ }
+ catch (DirectoryNotFoundException)
+ {
+ return (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType));
+ }
+ catch (FileNotFoundException)
+ {
+ return (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType));
+ }
+ catch (Exception ex)
+ {
+ return (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType));
}
}
diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs
index f618c8a25..5a9fc3322 100644
--- a/MediaBrowser.Controller/Channels/Channel.cs
+++ b/MediaBrowser.Controller/Channels/Channel.cs
@@ -14,7 +14,7 @@ namespace MediaBrowser.Controller.Channels
public override bool IsVisible(User user)
{
- if (user.Configuration.BlockedChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase))
+ if (user.Policy.BlockedChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase))
{
return false;
}
diff --git a/MediaBrowser.Controller/Channels/ChannelAudioItem.cs b/MediaBrowser.Controller/Channels/ChannelAudioItem.cs
index ede366dab..896d598bb 100644
--- a/MediaBrowser.Controller/Channels/ChannelAudioItem.cs
+++ b/MediaBrowser.Controller/Channels/ChannelAudioItem.cs
@@ -6,6 +6,7 @@ using MediaBrowser.Model.Entities;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
+using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Channels
{
@@ -25,8 +26,8 @@ namespace MediaBrowser.Controller.Channels
public string OriginalImageUrl { get; set; }
public List<ChannelMediaInfo> ChannelMediaSources { get; set; }
-
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.ChannelContent);
}
diff --git a/MediaBrowser.Controller/Channels/ChannelFolderItem.cs b/MediaBrowser.Controller/Channels/ChannelFolderItem.cs
index 5362cc195..8482e38df 100644
--- a/MediaBrowser.Controller/Channels/ChannelFolderItem.cs
+++ b/MediaBrowser.Controller/Channels/ChannelFolderItem.cs
@@ -5,6 +5,7 @@ using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Querying;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Channels
{
@@ -20,7 +21,7 @@ namespace MediaBrowser.Controller.Channels
public string OriginalImageUrl { get; set; }
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
// Don't block.
return false;
diff --git a/MediaBrowser.Controller/Channels/ChannelVideoItem.cs b/MediaBrowser.Controller/Channels/ChannelVideoItem.cs
index 72e2b110a..f0eafcbdf 100644
--- a/MediaBrowser.Controller/Channels/ChannelVideoItem.cs
+++ b/MediaBrowser.Controller/Channels/ChannelVideoItem.cs
@@ -8,6 +8,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
+using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Channels
{
@@ -51,7 +52,7 @@ namespace MediaBrowser.Controller.Channels
return ExternalId;
}
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.ChannelContent);
}
diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs
index efd24336a..f5010bb45 100644
--- a/MediaBrowser.Controller/Devices/IDeviceManager.cs
+++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs
@@ -85,5 +85,13 @@ namespace MediaBrowser.Controller.Devices
/// <param name="file">The file.</param>
/// <returns>Task.</returns>
Task AcceptCameraUpload(string deviceId, Stream stream, LocalFileInfo file);
+
+ /// <summary>
+ /// Determines whether this instance [can access device] the specified user identifier.
+ /// </summary>
+ /// <param name="userId">The user identifier.</param>
+ /// <param name="deviceId">The device identifier.</param>
+ /// <returns><c>true</c> if this instance [can access device] the specified user identifier; otherwise, <c>false</c>.</returns>
+ bool CanAccessDevice(string userId, string deviceId);
}
}
diff --git a/MediaBrowser.Model/Dto/DtoOptions.cs b/MediaBrowser.Controller/Dto/DtoOptions.cs
index 069d71fce..eeb4fc114 100644
--- a/MediaBrowser.Model/Dto/DtoOptions.cs
+++ b/MediaBrowser.Controller/Dto/DtoOptions.cs
@@ -1,11 +1,18 @@
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
+using System;
using System.Collections.Generic;
+using System.Linq;
-namespace MediaBrowser.Model.Dto
+namespace MediaBrowser.Controller.Dto
{
public class DtoOptions
{
+ private static readonly List<ItemFields> DefaultExcludedFields = new List<ItemFields>
+ {
+ ItemFields.SeasonUserData
+ };
+
public List<ItemFields> Fields { get; set; }
public List<ImageType> ImageTypes { get; set; }
public int ImageTypeLimit { get; set; }
@@ -14,9 +21,17 @@ namespace MediaBrowser.Model.Dto
public DtoOptions()
{
Fields = new List<ItemFields>();
- ImageTypes = new List<ImageType>();
ImageTypeLimit = int.MaxValue;
EnableImages = true;
+
+ Fields = Enum.GetNames(typeof (ItemFields))
+ .Select(i => (ItemFields) Enum.Parse(typeof (ItemFields), i, true))
+ .Except(DefaultExcludedFields)
+ .ToList();
+
+ ImageTypes = Enum.GetNames(typeof(ImageType))
+ .Select(i => (ImageType)Enum.Parse(typeof(ImageType), i, true))
+ .ToList();
}
public int GetImageLimit(ImageType type)
diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs
index 447328ea1..c5ed09016 100644
--- a/MediaBrowser.Controller/Entities/Audio/Audio.cs
+++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs
@@ -8,6 +8,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
+using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Entities.Audio
{
@@ -88,6 +89,21 @@ namespace MediaBrowser.Controller.Entities.Audio
}
}
+ [IgnoreDataMember]
+ public bool IsArchive
+ {
+ get
+ {
+ if (string.IsNullOrWhiteSpace(Path))
+ {
+ return false;
+ }
+ var ext = System.IO.Path.GetExtension(Path) ?? string.Empty;
+
+ return new[] { ".zip", ".rar", ".7z" }.Contains(ext, StringComparer.OrdinalIgnoreCase);
+ }
+ }
+
/// <summary>
/// Gets or sets the artist.
/// </summary>
@@ -173,7 +189,7 @@ namespace MediaBrowser.Controller.Entities.Audio
return base.GetUserDataKey();
}
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.Music);
}
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
index 1f7c62de0..90edfcce7 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
+using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Entities.Audio
{
@@ -154,7 +155,7 @@ namespace MediaBrowser.Controller.Entities.Audio
return base.GetUserDataKey();
}
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.Music);
}
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
index 2d9e052b1..a60258d1a 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
@@ -8,6 +8,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Entities.Audio
{
@@ -114,7 +115,7 @@ namespace MediaBrowser.Controller.Entities.Audio
return "Artist-" + item.Name;
}
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.Music);
}
@@ -135,7 +136,7 @@ namespace MediaBrowser.Controller.Entities.Audio
// Refresh songs
foreach (var item in songs)
{
- if (tasks.Count >= 3)
+ if (tasks.Count >= 2)
{
await Task.WhenAll(tasks).ConfigureAwait(false);
tasks.Clear();
@@ -172,37 +173,23 @@ namespace MediaBrowser.Controller.Entities.Audio
// Refresh all non-songs
foreach (var item in others)
{
- if (tasks.Count >= 3)
- {
- await Task.WhenAll(tasks).ConfigureAwait(false);
- tasks.Clear();
- }
-
cancellationToken.ThrowIfCancellationRequested();
- var innerProgress = new ActionableProgress<double>();
// Avoid implicitly captured closure
var currentChild = item;
- innerProgress.RegisterAction(p =>
- {
- lock (percentages)
- {
- percentages[currentChild.Id] = p / 100;
- var percent = percentages.Values.Sum();
- percent /= totalItems;
- percent *= 100;
- progress.Report(percent);
- }
- });
+ await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
+ lock (percentages)
+ {
+ percentages[currentChild.Id] = 1;
- // Avoid implicitly captured closure
- var taskChild = item;
- tasks.Add(Task.Run(async () => await RefreshItem(taskChild, refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false), cancellationToken));
+ var percent = percentages.Values.Sum();
+ percent /= totalItems;
+ percent *= 100;
+ progress.Report(percent);
+ }
}
- await Task.WhenAll(tasks).ConfigureAwait(false);
-
progress.Report(100);
}
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index ed950b1c5..ee562d8b4 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -14,6 +14,7 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Library;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Users;
using System;
using System.Collections.Generic;
using System.IO;
@@ -45,6 +46,8 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public static readonly string[] SupportedImageExtensions = { ".png", ".jpg", ".jpeg", ".tbn" };
+ public static readonly List<string> SupportedImageExtensionsList = SupportedImageExtensions.ToList();
+
/// <summary>
/// The trailer folder name
/// </summary>
@@ -593,7 +596,7 @@ namespace MediaBrowser.Controller.Entities
/// <returns>PlayAccess.</returns>
public PlayAccess GetPlayAccess(User user)
{
- if (!user.Configuration.EnableMediaPlayback)
+ if (!user.Policy.EnableMediaPlayback)
{
return PlayAccess.None;
}
@@ -985,7 +988,7 @@ namespace MediaBrowser.Controller.Entities
return false;
}
- var maxAllowedRating = user.Configuration.MaxParentalRating;
+ var maxAllowedRating = user.Policy.MaxParentalRating;
if (maxAllowedRating == null)
{
@@ -1001,7 +1004,7 @@ namespace MediaBrowser.Controller.Entities
if (string.IsNullOrWhiteSpace(rating))
{
- return !GetBlockUnratedValue(user.Configuration);
+ return !GetBlockUnratedValue(user.Policy);
}
var value = LocalizationManager.GetRatingLevel(rating);
@@ -1021,7 +1024,7 @@ namespace MediaBrowser.Controller.Entities
if (hasTags != null)
{
- if (user.Configuration.BlockedTags.Any(i => hasTags.Tags.Contains(i, StringComparer.OrdinalIgnoreCase)))
+ if (user.Policy.BlockedTags.Any(i => hasTags.Tags.Contains(i, StringComparer.OrdinalIgnoreCase)))
{
return false;
}
@@ -1035,7 +1038,7 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
/// <param name="config">The configuration.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
- protected virtual bool GetBlockUnratedValue(UserConfiguration config)
+ protected virtual bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.Other);
}
@@ -1574,6 +1577,11 @@ namespace MediaBrowser.Controller.Entities
foreach (var newImage in images)
{
+ if (newImage == null)
+ {
+ throw new ArgumentException("null image found in list");
+ }
+
var existing = existingImages
.FirstOrDefault(i => string.Equals(i.Path, newImage.FullName, StringComparison.OrdinalIgnoreCase));
diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs
index ea7ecfb4a..381b2101d 100644
--- a/MediaBrowser.Controller/Entities/Book.cs
+++ b/MediaBrowser.Controller/Entities/Book.cs
@@ -2,6 +2,7 @@
using MediaBrowser.Model.Configuration;
using System.Collections.Generic;
using System.Linq;
+using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Entities
{
@@ -36,7 +37,7 @@ namespace MediaBrowser.Controller.Entities
Tags = new List<string>();
}
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.Book);
}
diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs
index a10742f01..f47a439a7 100644
--- a/MediaBrowser.Controller/Entities/CollectionFolder.cs
+++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs
@@ -66,6 +66,22 @@ namespace MediaBrowser.Controller.Entities
return CreateResolveArgs(directoryService).FileSystemChildren;
}
+ internal override bool IsValidFromResolver(BaseItem newItem)
+ {
+ var newCollectionFolder = newItem as CollectionFolder;
+
+ if (newCollectionFolder != null)
+ {
+ if (!string.Equals(CollectionType, newCollectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+ }
+
+
+ return base.IsValidFromResolver(newItem);
+ }
+
private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService)
{
var path = ContainingFolderPath;
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 87ad9c380..2761aa5d7 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -303,10 +303,10 @@ namespace MediaBrowser.Controller.Entities
{
if (this is ICollectionFolder)
{
- if (user.Configuration.BlockedMediaFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase) ||
+ if (user.Policy.BlockedMediaFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase) ||
// Backwards compatibility
- user.Configuration.BlockedMediaFolders.Contains(Name, StringComparer.OrdinalIgnoreCase))
+ user.Policy.BlockedMediaFolders.Contains(Name, StringComparer.OrdinalIgnoreCase))
{
return false;
}
@@ -545,7 +545,7 @@ namespace MediaBrowser.Controller.Entities
foreach (var child in children)
{
- if (tasks.Count >= 3)
+ if (tasks.Count >= 2)
{
await Task.WhenAll(tasks).ConfigureAwait(false);
tasks.Clear();
@@ -708,7 +708,7 @@ namespace MediaBrowser.Controller.Entities
/// <returns>IEnumerable{BaseItem}.</returns>
protected virtual IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
{
- var collectionType = LibraryManager.FindCollectionType(this);
+ var collectionType = LibraryManager.GetContentType(this);
return LibraryManager.ResolvePaths(GetFileSystemChildren(directoryService), directoryService, this, collectionType);
}
diff --git a/MediaBrowser.Controller/Entities/Game.cs b/MediaBrowser.Controller/Entities/Game.cs
index e4d032359..bf32d3e63 100644
--- a/MediaBrowser.Controller/Entities/Game.cs
+++ b/MediaBrowser.Controller/Entities/Game.cs
@@ -4,6 +4,7 @@ using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
+using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Entities
{
@@ -108,7 +109,7 @@ namespace MediaBrowser.Controller.Entities
return base.GetDeletePaths();
}
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.Game);
}
diff --git a/MediaBrowser.Controller/Entities/GameSystem.cs b/MediaBrowser.Controller/Entities/GameSystem.cs
index f2fec4397..758498977 100644
--- a/MediaBrowser.Controller/Entities/GameSystem.cs
+++ b/MediaBrowser.Controller/Entities/GameSystem.cs
@@ -2,6 +2,7 @@
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using System;
+using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Entities
{
@@ -43,7 +44,7 @@ namespace MediaBrowser.Controller.Entities
return base.GetUserDataKey();
}
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
// Don't block. Determine by game
return false;
diff --git a/MediaBrowser.Controller/Entities/IHasMediaSources.cs b/MediaBrowser.Controller/Entities/IHasMediaSources.cs
index d487362f5..98d268298 100644
--- a/MediaBrowser.Controller/Entities/IHasMediaSources.cs
+++ b/MediaBrowser.Controller/Entities/IHasMediaSources.cs
@@ -49,8 +49,8 @@ namespace MediaBrowser.Controller.Entities
: new[] { user.Configuration.AudioLanguagePreference };
var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference)
- ? new string[] { }
- : new[] { user.Configuration.SubtitleLanguagePreference };
+ ? new List<string> { }
+ : new List<string> { user.Configuration.SubtitleLanguagePreference };
foreach (var source in sources)
{
diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
index 9dc600675..4483c7b0f 100644
--- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
+++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
@@ -9,6 +9,7 @@ using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Entities.Movies
{
@@ -18,7 +19,7 @@ namespace MediaBrowser.Controller.Entities.Movies
public class BoxSet : Folder, IHasTrailers, IHasKeywords, IHasPreferredMetadataLanguage, IHasDisplayOrder, IHasLookupInfo<BoxSetInfo>, IMetadataContainer, IHasShares
{
public List<Share> Shares { get; set; }
-
+
public BoxSet()
{
RemoteTrailers = new List<MediaUrl>();
@@ -67,7 +68,7 @@ namespace MediaBrowser.Controller.Entities.Movies
/// <value>The display order.</value>
public string DisplayOrder { get; set; }
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.Movie);
}
@@ -170,10 +171,13 @@ namespace MediaBrowser.Controller.Entities.Movies
{
var userId = user.Id.ToString("N");
- return Shares.Any(i => string.Equals(userId, i.UserId, StringComparison.OrdinalIgnoreCase)) ||
+ // Need to check Count > 0 for boxsets created prior to the introduction of Shares
+ if (Shares.Count > 0 && !Shares.Any(i => string.Equals(userId, i.UserId, StringComparison.OrdinalIgnoreCase)))
+ {
+ //return false;
+ }
- // Need to support this for boxsets created prior to the creation of Shares
- Shares.Count == 0;
+ return true;
}
return false;
diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs
index e749d89e4..b3774cfe0 100644
--- a/MediaBrowser.Controller/Entities/Movies/Movie.cs
+++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs
@@ -1,6 +1,7 @@
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Users;
using System;
using System.Collections.Generic;
using System.IO;
@@ -146,14 +147,21 @@ namespace MediaBrowser.Controller.Entities.Movies
return itemsChanged;
}
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.Movie);
}
public MovieInfo GetLookupInfo()
{
- return GetItemLookupInfo<MovieInfo>();
+ var info = GetItemLookupInfo<MovieInfo>();
+
+ if (!IsInMixedFolder)
+ {
+ info.Name = System.IO.Path.GetFileName(ContainingFolderPath);
+ }
+
+ return info;
}
public override bool BeforeMetadataRefresh()
diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs
index d7cd62aa6..4ca8cf1c5 100644
--- a/MediaBrowser.Controller/Entities/MusicVideo.cs
+++ b/MediaBrowser.Controller/Entities/MusicVideo.cs
@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
+using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Entities
{
@@ -80,7 +81,7 @@ namespace MediaBrowser.Controller.Entities
return this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? base.GetUserDataKey();
}
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.Music);
}
diff --git a/MediaBrowser.Controller/Entities/Photo.cs b/MediaBrowser.Controller/Entities/Photo.cs
index 367db5dcb..a3d892181 100644
--- a/MediaBrowser.Controller/Entities/Photo.cs
+++ b/MediaBrowser.Controller/Entities/Photo.cs
@@ -3,6 +3,7 @@ using MediaBrowser.Model.Drawing;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
+using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Entities
{
@@ -69,8 +70,8 @@ namespace MediaBrowser.Controller.Entities
public double? Longitude { get; set; }
public double? Altitude { get; set; }
public int? IsoSpeedRating { get; set; }
-
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.Other);
}
diff --git a/MediaBrowser.Controller/Entities/PhotoAlbum.cs b/MediaBrowser.Controller/Entities/PhotoAlbum.cs
index 982b1ef17..24ebf8815 100644
--- a/MediaBrowser.Controller/Entities/PhotoAlbum.cs
+++ b/MediaBrowser.Controller/Entities/PhotoAlbum.cs
@@ -1,6 +1,7 @@
using MediaBrowser.Model.Configuration;
using System.Linq;
using System.Runtime.Serialization;
+using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Entities
{
@@ -22,8 +23,8 @@ namespace MediaBrowser.Controller.Entities
return true;
}
}
-
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.Other);
}
diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs
index cc0fc6812..6b67cebc8 100644
--- a/MediaBrowser.Controller/Entities/TV/Episode.cs
+++ b/MediaBrowser.Controller/Entities/TV/Episode.cs
@@ -1,7 +1,7 @@
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
+using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Users;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -179,6 +179,15 @@ namespace MediaBrowser.Controller.Entities.TV
}
[IgnoreDataMember]
+ public bool IsInSeasonFolder
+ {
+ get
+ {
+ return FindParent<Season>() != null;
+ }
+ }
+
+ [IgnoreDataMember]
public string SeriesName
{
get
@@ -275,7 +284,7 @@ namespace MediaBrowser.Controller.Entities.TV
return new[] { Path };
}
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.Series);
}
@@ -301,51 +310,9 @@ namespace MediaBrowser.Controller.Entities.TV
{
var hasChanges = base.BeforeMetadataRefresh();
- var locationType = LocationType;
- if (locationType == LocationType.FileSystem || locationType == LocationType.Offline)
- {
- if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path))
- {
- IndexNumber = LibraryManager.GetEpisodeNumberFromFile(Path, true);
-
- // If a change was made record it
- if (IndexNumber.HasValue)
- {
- hasChanges = true;
- }
- }
-
- if (!IndexNumberEnd.HasValue && !string.IsNullOrEmpty(Path))
- {
- IndexNumberEnd = LibraryManager.GetEndingEpisodeNumberFromFile(Path);
-
- // If a change was made record it
- if (IndexNumberEnd.HasValue)
- {
- hasChanges = true;
- }
- }
- }
-
- if (!ParentIndexNumber.HasValue)
+ if (LibraryManager.FillMissingEpisodeNumbersFromPath(this))
{
- var season = Season;
-
- if (season != null)
- {
- ParentIndexNumber = season.IndexNumber;
- }
-
- if (!ParentIndexNumber.HasValue && !string.IsNullOrEmpty(Path))
- {
- ParentIndexNumber = LibraryManager.GetSeasonNumberFromEpisodeFile(Path);
- }
-
- // If a change was made record it
- if (ParentIndexNumber.HasValue)
- {
- hasChanges = true;
- }
+ hasChanges = true;
}
return hasChanges;
diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs
index 2df90244c..54db12b6f 100644
--- a/MediaBrowser.Controller/Entities/TV/Season.cs
+++ b/MediaBrowser.Controller/Entities/TV/Season.cs
@@ -1,9 +1,9 @@
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Localization;
+using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
+using MediaBrowser.Model.Users;
+using MoreLinq;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
@@ -155,24 +155,6 @@ namespace MediaBrowser.Controller.Entities.TV
return IndexNumber != null ? IndexNumber.Value.ToString("0000") : Name;
}
- private IEnumerable<Episode> GetEpisodes()
- {
- var series = Series;
-
- if (series != null && series.ContainsEpisodesWithoutSeasonFolders)
- {
- var seasonNumber = IndexNumber;
-
- if (seasonNumber.HasValue)
- {
- return series.RecursiveChildren.OfType<Episode>()
- .Where(i => i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == seasonNumber.Value);
- }
- }
-
- return Children.OfType<Episode>();
- }
-
[IgnoreDataMember]
public bool IsMissingSeason
{
@@ -220,16 +202,32 @@ namespace MediaBrowser.Controller.Entities.TV
var episodes = GetRecursiveChildren(user)
.OfType<Episode>();
- if (IndexNumber.HasValue)
+ var series = Series;
+
+ if (IndexNumber.HasValue && series != null)
{
- var series = Series;
+ return series.GetEpisodes(user, IndexNumber.Value, includeMissingEpisodes, includeVirtualUnairedEpisodes, episodes);
+ }
- if (series != null)
+ if (series != null && series.ContainsEpisodesWithoutSeasonFolders)
+ {
+ var seasonNumber = IndexNumber;
+ var list = episodes.ToList();
+
+ if (seasonNumber.HasValue)
{
- return series.GetEpisodes(user, IndexNumber.Value, includeMissingEpisodes, includeVirtualUnairedEpisodes, episodes);
+ list.AddRange(series.GetRecursiveChildren(user).OfType<Episode>()
+ .Where(i => i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == seasonNumber.Value));
+ }
+ else
+ {
+ list.AddRange(series.GetRecursiveChildren(user).OfType<Episode>()
+ .Where(i => !i.ParentIndexNumber.HasValue));
}
- }
+ episodes = list.DistinctBy(i => i.Id);
+ }
+
if (!includeMissingEpisodes)
{
episodes = episodes.Where(i => !i.IsMissingEpisode);
@@ -244,12 +242,39 @@ namespace MediaBrowser.Controller.Entities.TV
.Cast<Episode>();
}
+ private IEnumerable<Episode> GetEpisodes()
+ {
+ var episodes = RecursiveChildren.OfType<Episode>();
+ var series = Series;
+
+ if (series != null && series.ContainsEpisodesWithoutSeasonFolders)
+ {
+ var seasonNumber = IndexNumber;
+ var list = episodes.ToList();
+
+ if (seasonNumber.HasValue)
+ {
+ list.AddRange(series.RecursiveChildren.OfType<Episode>()
+ .Where(i => i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == seasonNumber.Value));
+ }
+ else
+ {
+ list.AddRange(series.RecursiveChildren.OfType<Episode>()
+ .Where(i => !i.ParentIndexNumber.HasValue));
+ }
+
+ episodes = list.DistinctBy(i => i.Id);
+ }
+
+ return episodes;
+ }
+
public override IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
{
return GetEpisodes(user);
}
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
// Don't block. Let either the entire series rating or episode rating determine it
return false;
diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs
index 4c0d1fdfb..55cfffeb2 100644
--- a/MediaBrowser.Controller/Entities/TV/Series.cs
+++ b/MediaBrowser.Controller/Entities/TV/Series.cs
@@ -3,6 +3,7 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
+using MediaBrowser.Model.Users;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -87,7 +88,17 @@ namespace MediaBrowser.Controller.Entities.TV
/// Gets or sets the date last episode added.
/// </summary>
/// <value>The date last episode added.</value>
- public DateTime DateLastEpisodeAdded { get; set; }
+ [IgnoreDataMember]
+ public DateTime DateLastEpisodeAdded
+ {
+ get
+ {
+ return RecursiveChildren.OfType<Episode>()
+ .Select(i => i.DateCreated)
+ .OrderByDescending(i => i)
+ .FirstOrDefault();
+ }
+ }
/// <summary>
/// Series aren't included directly in indices - Their Episodes will roll up to them
@@ -246,7 +257,7 @@ namespace MediaBrowser.Controller.Entities.TV
});
}
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.Series);
}
diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs
index bb165d790..7a1eef8db 100644
--- a/MediaBrowser.Controller/Entities/Trailer.cs
+++ b/MediaBrowser.Controller/Entities/Trailer.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.Serialization;
+using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Entities
{
@@ -98,7 +99,7 @@ namespace MediaBrowser.Controller.Entities
return base.GetUserDataKey();
}
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.Trailer);
}
diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs
index 3dfc8cc7d..626afcfdf 100644
--- a/MediaBrowser.Controller/Entities/User.cs
+++ b/MediaBrowser.Controller/Entities/User.cs
@@ -1,5 +1,4 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Connect;
@@ -107,37 +106,27 @@ namespace MediaBrowser.Controller.Entities
/// <value>The last activity date.</value>
public DateTime? LastActivityDate { get; set; }
- /// <summary>
- /// The _configuration
- /// </summary>
- private UserConfiguration _configuration;
- /// <summary>
- /// The _configuration initialized
- /// </summary>
- private bool _configurationInitialized;
- /// <summary>
- /// The _configuration sync lock
- /// </summary>
- private object _configurationSyncLock = new object();
- /// <summary>
- /// Gets the user's configuration
- /// </summary>
- /// <value>The configuration.</value>
+ private UserConfiguration _config;
+ private readonly object _configSyncLock = new object();
[IgnoreDataMember]
public UserConfiguration Configuration
{
get
{
- // Lazy load
- LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationInitialized, ref _configurationSyncLock, () => (UserConfiguration)ConfigurationHelper.GetXmlConfiguration(typeof(UserConfiguration), ConfigurationFilePath, XmlSerializer));
- return _configuration;
- }
- private set
- {
- _configuration = value;
+ if (_config == null)
+ {
+ lock (_configSyncLock)
+ {
+ if (_config == null)
+ {
+ _config = UserManager.GetUserConfiguration(this);
+ }
+ }
+ }
- _configurationInitialized = value != null;
+ return _config;
}
+ set { _config = value; }
}
private UserPolicy _policy;
@@ -256,35 +245,6 @@ namespace MediaBrowser.Controller.Entities
return System.IO.Path.Combine(parentPath, Id.ToString("N"));
}
- /// <summary>
- /// Gets the path to the user's configuration file
- /// </summary>
- /// <value>The configuration file path.</value>
- [IgnoreDataMember]
- public string ConfigurationFilePath
- {
- get
- {
- return System.IO.Path.Combine(ConfigurationDirectoryPath, "config.xml");
- }
- }
-
- /// <summary>
- /// Updates the configuration.
- /// </summary>
- /// <param name="config">The config.</param>
- /// <exception cref="System.ArgumentNullException">config</exception>
- public void UpdateConfiguration(UserConfiguration config)
- {
- if (config == null)
- {
- throw new ArgumentNullException("config");
- }
-
- Configuration = config;
- UserManager.UpdateConfiguration(this, Configuration);
- }
-
public bool IsParentalScheduleAllowed()
{
return IsParentalScheduleAllowed(DateTime.UtcNow);
@@ -292,7 +252,7 @@ namespace MediaBrowser.Controller.Entities
public bool IsParentalScheduleAllowed(DateTime date)
{
- var schedules = Configuration.AccessSchedules;
+ var schedules = Policy.AccessSchedules;
if (schedules.Length == 0)
{
diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs
index 926ffa19c..0364ff678 100644
--- a/MediaBrowser.Controller/Entities/UserView.cs
+++ b/MediaBrowser.Controller/Entities/UserView.cs
@@ -63,7 +63,8 @@ namespace MediaBrowser.Controller.Entities
{
CollectionType.Books,
CollectionType.HomeVideos,
- CollectionType.Photos
+ CollectionType.Photos,
+ string.Empty
};
var collectionFolder = folder as ICollectionFolder;
diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs
index 0c6125dbe..3abaf095c 100644
--- a/MediaBrowser.Controller/Entities/Video.cs
+++ b/MediaBrowser.Controller/Entities/Video.cs
@@ -91,6 +91,21 @@ namespace MediaBrowser.Controller.Entities
get { return LocalAlternateVersions.Count > 0; }
}
+ [IgnoreDataMember]
+ public bool IsArchive
+ {
+ get
+ {
+ if (string.IsNullOrWhiteSpace(Path))
+ {
+ return false;
+ }
+ var ext = System.IO.Path.GetExtension(Path) ?? string.Empty;
+
+ return new[] { ".zip", ".rar", ".7z" }.Contains(ext, StringComparer.OrdinalIgnoreCase);
+ }
+ }
+
public IEnumerable<Guid> GetAdditionalPartIds()
{
return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
@@ -246,7 +261,7 @@ namespace MediaBrowser.Controller.Entities
{
return System.IO.Path.GetFileName(Path);
}
-
+
return System.IO.Path.GetFileNameWithoutExtension(Path);
}
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index 33dea4dca..8573f32e0 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -1,5 +1,6 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Controller.Sorting;
@@ -22,11 +23,9 @@ namespace MediaBrowser.Controller.Library
/// </summary>
/// <param name="fileInfo">The file information.</param>
/// <param name="parent">The parent.</param>
- /// <param name="collectionType">Type of the collection.</param>
/// <returns>BaseItem.</returns>
BaseItem ResolvePath(FileSystemInfo fileInfo,
- Folder parent = null,
- string collectionType = null);
+ Folder parent = null);
/// <summary>
/// Resolves a set of files into a list of BaseItem
@@ -258,9 +257,16 @@ namespace MediaBrowser.Controller.Library
/// </summary>
/// <param name="item">The item.</param>
/// <returns>System.String.</returns>
- string FindCollectionType(BaseItem item);
+ string GetContentType(BaseItem item);
/// <summary>
+ /// Gets the type of the inherited content.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <returns>System.String.</returns>
+ string GetInheritedContentType(BaseItem item);
+
+ /// <summary>
/// Normalizes the root path list.
/// </summary>
/// <param name="paths">The paths.</param>
@@ -340,26 +346,11 @@ namespace MediaBrowser.Controller.Library
int? GetSeasonNumberFromPath(string path);
/// <summary>
- /// Gets the season number from episode file.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns>System.Nullable&lt;System.Int32&gt;.</returns>
- int? GetSeasonNumberFromEpisodeFile(string path);
-
- /// <summary>
- /// Gets the ending episode number from file.
+ /// Fills the missing episode numbers from path.
/// </summary>
- /// <param name="path">The path.</param>
- /// <returns>System.Nullable&lt;System.Int32&gt;.</returns>
- int? GetEndingEpisodeNumberFromFile(string path);
-
- /// <summary>
- /// Gets the episode number from file.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="considerSeasonless">if set to <c>true</c> [consider seasonless].</param>
- /// <returns>System.Nullable&lt;System.Int32&gt;.</returns>
- int? GetEpisodeNumberFromFile(string path, bool considerSeasonless);
+ /// <param name="episode">The episode.</param>
+ /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
+ bool FillMissingEpisodeNumbersFromPath(Episode episode);
/// <summary>
/// Parses the name.
diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs
index 226f77525..06e7d1763 100644
--- a/MediaBrowser.Controller/Library/IUserDataManager.cs
+++ b/MediaBrowser.Controller/Library/IUserDataManager.cs
@@ -61,5 +61,9 @@ namespace MediaBrowser.Controller.Library
/// <returns></returns>
Task SaveAllUserData(Guid userId, IEnumerable<UserItemData> userData, CancellationToken cancellationToken);
+ /// <summary>
+ /// Updates playstate for an item and returns true or false indicating if it was played to completion
+ /// </summary>
+ bool UpdatePlayState(BaseItem item, UserItemData data, long positionTicks);
}
}
diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs
index 9dc16ba4d..f5846973e 100644
--- a/MediaBrowser.Controller/Library/IUserManager.cs
+++ b/MediaBrowser.Controller/Library/IUserManager.cs
@@ -36,13 +36,6 @@ namespace MediaBrowser.Controller.Library
event EventHandler<GenericEventArgs<User>> UserPasswordChanged;
/// <summary>
- /// Updates the configuration.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <param name="newConfiguration">The new configuration.</param>
- void UpdateConfiguration(User user, UserConfiguration newConfiguration);
-
- /// <summary>
/// Gets a User by Id
/// </summary>
/// <param name="id">The id.</param>
@@ -173,10 +166,32 @@ namespace MediaBrowser.Controller.Library
UserPolicy GetUserPolicy(User user);
/// <summary>
+ /// Gets the user configuration.
+ /// </summary>
+ /// <param name="user">The user.</param>
+ /// <returns>UserConfiguration.</returns>
+ UserConfiguration GetUserConfiguration(User user);
+
+ /// <summary>
+ /// Updates the configuration.
+ /// </summary>
+ /// <param name="userId">The user identifier.</param>
+ /// <param name="newConfiguration">The new configuration.</param>
+ /// <returns>Task.</returns>
+ Task UpdateConfiguration(string userId, UserConfiguration newConfiguration);
+
+ /// <summary>
/// Updates the user policy.
/// </summary>
/// <param name="userId">The user identifier.</param>
/// <param name="userPolicy">The user policy.</param>
Task UpdateUserPolicy(string userId, UserPolicy userPolicy);
+
+ /// <summary>
+ /// Makes the valid username.
+ /// </summary>
+ /// <param name="username">The username.</param>
+ /// <returns>System.String.</returns>
+ string MakeValidUsername(string username);
}
}
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs
index 9f8d67a48..b95d67ad8 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs
@@ -2,6 +2,7 @@
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using System.Linq;
+using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.LiveTv
{
@@ -78,7 +79,7 @@ namespace MediaBrowser.Controller.LiveTv
}
}
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.LiveTvProgram);
}
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
index df118b25f..de72accff 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
@@ -6,6 +6,7 @@ using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.MediaInfo;
using System.Collections.Generic;
using System.Linq;
+using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.LiveTv
{
@@ -33,7 +34,7 @@ namespace MediaBrowser.Controller.LiveTv
}
}
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.LiveTvChannel);
}
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
index 266eaabee..29b23a551 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
@@ -6,6 +6,7 @@ using System;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
+using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.LiveTv
{
@@ -199,7 +200,7 @@ namespace MediaBrowser.Controller.LiveTv
return ItemRepository.SaveItem(this, cancellationToken);
}
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.LiveTvProgram);
}
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs
index 66de81213..6fc985643 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs
@@ -2,6 +2,7 @@
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using System.Linq;
+using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.LiveTv
{
@@ -78,7 +79,7 @@ namespace MediaBrowser.Controller.LiveTv
}
}
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.LiveTvProgram);
}
diff --git a/MediaBrowser.Controller/LiveTv/RecordingGroup.cs b/MediaBrowser.Controller/LiveTv/RecordingGroup.cs
index 7bd810b8d..d7250d9d2 100644
--- a/MediaBrowser.Controller/LiveTv/RecordingGroup.cs
+++ b/MediaBrowser.Controller/LiveTv/RecordingGroup.cs
@@ -1,11 +1,12 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.LiveTv
{
public class RecordingGroup : Folder
{
- protected override bool GetBlockUnratedValue(UserConfiguration config)
+ protected override bool GetBlockUnratedValue(UserPolicy config)
{
// Don't block.
return false;
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index c198a58d4..0667730fd 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -115,6 +115,7 @@
<Compile Include="Drawing\ImageProcessingOptions.cs" />
<Compile Include="Drawing\ImageProcessorExtensions.cs" />
<Compile Include="Drawing\ImageStream.cs" />
+ <Compile Include="Dto\DtoOptions.cs" />
<Compile Include="Dto\IDtoService.cs" />
<Compile Include="Entities\AdultVideo.cs" />
<Compile Include="Entities\Audio\IHasAlbumArtist.cs" />
@@ -198,18 +199,17 @@
<Compile Include="LiveTv\SeriesTimerInfo.cs" />
<Compile Include="LiveTv\TimerInfo.cs" />
<Compile Include="Localization\ILocalizationManager.cs" />
- <Compile Include="MediaEncoding\EncodingOptions.cs" />
<Compile Include="MediaEncoding\ChapterImageRefreshOptions.cs" />
- <Compile Include="MediaEncoding\EncodingResult.cs" />
+ <Compile Include="MediaEncoding\EncodingJobOptions.cs" />
<Compile Include="MediaEncoding\IEncodingManager.cs" />
<Compile Include="MediaEncoding\ImageEncodingOptions.cs" />
<Compile Include="MediaEncoding\IMediaEncoder.cs" />
<Compile Include="MediaEncoding\InternalMediaInfoResult.cs" />
<Compile Include="MediaEncoding\ISubtitleEncoder.cs" />
<Compile Include="MediaEncoding\MediaStreamSelector.cs" />
- <Compile Include="MediaEncoding\VideoEncodingOptions.cs" />
<Compile Include="Net\AuthenticatedAttribute.cs" />
<Compile Include="Net\AuthorizationInfo.cs" />
+ <Compile Include="Net\BasePeriodicWebSocketListener.cs" />
<Compile Include="Net\IAuthorizationContext.cs" />
<Compile Include="Net\IAuthService.cs" />
<Compile Include="Net\IHasAuthorization.cs" />
@@ -221,10 +221,15 @@
<Compile Include="Net\IServerManager.cs" />
<Compile Include="Net\IServiceRequest.cs" />
<Compile Include="Net\ISessionContext.cs" />
+ <Compile Include="Net\IWebSocket.cs" />
+ <Compile Include="Net\IWebSocketConnection.cs" />
+ <Compile Include="Net\IWebSocketListener.cs" />
<Compile Include="Net\LoggedAttribute.cs" />
<Compile Include="Net\SecurityException.cs" />
<Compile Include="Net\ServiceStackServiceRequest.cs" />
<Compile Include="Net\StaticResultOptions.cs" />
+ <Compile Include="Net\WebSocketConnectEventArgs.cs" />
+ <Compile Include="Net\WebSocketMessageInfo.cs" />
<Compile Include="News\INewsService.cs" />
<Compile Include="Notifications\INotificationManager.cs" />
<Compile Include="Notifications\INotificationService.cs" />
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
new file mode 100644
index 000000000..a988c2f97
--- /dev/null
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
@@ -0,0 +1,91 @@
+using MediaBrowser.Model.Dlna;
+
+namespace MediaBrowser.Controller.MediaEncoding
+{
+ public class EncodingJobOptions
+ {
+ public string OutputContainer { get; set; }
+
+ public long? StartTimeTicks { get; set; }
+ public int? Width { get; set; }
+ public int? Height { get; set; }
+ public int? MaxWidth { get; set; }
+ public int? MaxHeight { get; set; }
+ public bool Static = false;
+ public float? Framerate { get; set; }
+ public float? MaxFramerate { get; set; }
+ public string Profile { get; set; }
+ public int? Level { get; set; }
+
+ public string DeviceId { get; set; }
+ public string ItemId { get; set; }
+ public string MediaSourceId { get; set; }
+ public string AudioCodec { get; set; }
+
+ public bool EnableAutoStreamCopy { get; set; }
+
+ public int? MaxAudioChannels { get; set; }
+ public int? AudioChannels { get; set; }
+ public int? AudioBitRate { get; set; }
+ public int? AudioSampleRate { get; set; }
+
+ public DeviceProfile DeviceProfile { get; set; }
+ public EncodingContext Context { get; set; }
+
+ public string VideoCodec { get; set; }
+
+ public int? VideoBitRate { get; set; }
+ public int? AudioStreamIndex { get; set; }
+ public int? VideoStreamIndex { get; set; }
+ public int? SubtitleStreamIndex { get; set; }
+ public int? MaxRefFrames { get; set; }
+ public int? MaxVideoBitDepth { get; set; }
+ public SubtitleDeliveryMethod SubtitleMethod { get; set; }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance has fixed resolution.
+ /// </summary>
+ /// <value><c>true</c> if this instance has fixed resolution; otherwise, <c>false</c>.</value>
+ public bool HasFixedResolution
+ {
+ get
+ {
+ return Width.HasValue || Height.HasValue;
+ }
+ }
+
+ public bool? Cabac { get; set; }
+
+ public EncodingJobOptions()
+ {
+
+ }
+
+ public EncodingJobOptions(StreamInfo info, DeviceProfile deviceProfile)
+ {
+ OutputContainer = info.Container;
+ StartTimeTicks = info.StartPositionTicks;
+ MaxWidth = info.MaxWidth;
+ MaxHeight = info.MaxHeight;
+ MaxFramerate = info.MaxFramerate;
+ Profile = info.VideoProfile;
+ Level = info.VideoLevel;
+ ItemId = info.ItemId;
+ MediaSourceId = info.MediaSourceId;
+ AudioCodec = info.AudioCodec;
+ MaxAudioChannels = info.MaxAudioChannels;
+ AudioBitRate = info.AudioBitrate;
+ AudioSampleRate = info.TargetAudioSampleRate;
+ DeviceProfile = deviceProfile;
+ VideoCodec = info.VideoCodec;
+ VideoBitRate = info.VideoBitrate;
+ AudioStreamIndex = info.AudioStreamIndex;
+ SubtitleStreamIndex = info.SubtitleStreamIndex;
+ MaxRefFrames = info.MaxRefFrames;
+ MaxVideoBitDepth = info.MaxVideoBitDepth;
+ SubtitleMethod = info.SubtitleDeliveryMethod;
+ Cabac = info.Cabac;
+ Context = info.Context;
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingOptions.cs
deleted file mode 100644
index 26182ebc4..000000000
--- a/MediaBrowser.Controller/MediaEncoding/EncodingOptions.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Model.Dlna;
-
-namespace MediaBrowser.Controller.MediaEncoding
-{
- public class EncodingOptions
- {
- /// <summary>
- /// Gets or sets the item identifier.
- /// </summary>
- /// <value>The item identifier.</value>
- public string ItemId { get; set; }
-
- /// <summary>
- /// Gets or sets the media source identifier.
- /// </summary>
- /// <value>The media source identifier.</value>
- public string MediaSourceId { get; set; }
-
- /// <summary>
- /// Gets or sets the device profile.
- /// </summary>
- /// <value>The device profile.</value>
- public DeviceProfile DeviceProfile { get; set; }
-
- /// <summary>
- /// Gets or sets the output path.
- /// </summary>
- /// <value>The output path.</value>
- public string OutputPath { get; set; }
-
- /// <summary>
- /// Gets or sets the container.
- /// </summary>
- /// <value>The container.</value>
- public string Container { get; set; }
-
- /// <summary>
- /// Gets or sets the audio codec.
- /// </summary>
- /// <value>The audio codec.</value>
- public string AudioCodec { get; set; }
-
- /// <summary>
- /// Gets or sets the start time ticks.
- /// </summary>
- /// <value>The start time ticks.</value>
- public long? StartTimeTicks { get; set; }
-
- /// <summary>
- /// Gets or sets the maximum channels.
- /// </summary>
- /// <value>The maximum channels.</value>
- public int? MaxAudioChannels { get; set; }
-
- /// <summary>
- /// Gets or sets the channels.
- /// </summary>
- /// <value>The channels.</value>
- public int? AudioChannels { get; set; }
-
- /// <summary>
- /// Gets or sets the sample rate.
- /// </summary>
- /// <value>The sample rate.</value>
- public int? AudioSampleRate { get; set; }
-
- /// <summary>
- /// Gets or sets the bit rate.
- /// </summary>
- /// <value>The bit rate.</value>
- public int? AudioBitRate { get; set; }
-
- /// <summary>
- /// Gets or sets the maximum audio bit rate.
- /// </summary>
- /// <value>The maximum audio bit rate.</value>
- public int? MaxAudioBitRate { get; set; }
- }
-}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingResult.cs b/MediaBrowser.Controller/MediaEncoding/EncodingResult.cs
deleted file mode 100644
index 75ee90e42..000000000
--- a/MediaBrowser.Controller/MediaEncoding/EncodingResult.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Controller.MediaEncoding
-{
- public class EncodingResult
- {
- public string OutputPath { get; set; }
- }
-}
diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
index 38c2c83c4..47544f972 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -96,5 +96,27 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <param name="ticks">The ticks.</param>
/// <returns>System.String.</returns>
string GetTimeParameter(long ticks);
+
+ /// <summary>
+ /// Encodes the audio.
+ /// </summary>
+ /// <param name="options">The options.</param>
+ /// <param name="progress">The progress.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ Task<string> EncodeAudio(EncodingJobOptions options,
+ IProgress<double> progress,
+ CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Encodes the video.
+ /// </summary>
+ /// <param name="options">The options.</param>
+ /// <param name="progress">The progress.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task&lt;System.String&gt;.</returns>
+ Task<string> EncodeVideo(EncodingJobOptions options,
+ IProgress<double> progress,
+ CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/MediaStreamSelector.cs b/MediaBrowser.Controller/MediaEncoding/MediaStreamSelector.cs
index 58a68c257..4a807df7a 100644
--- a/MediaBrowser.Controller/MediaEncoding/MediaStreamSelector.cs
+++ b/MediaBrowser.Controller/MediaEncoding/MediaStreamSelector.cs
@@ -34,15 +34,13 @@ namespace MediaBrowser.Controller.MediaEncoding
}
public static int? GetDefaultSubtitleStreamIndex(List<MediaStream> streams,
- IEnumerable<string> preferredLanguages,
+ List<string> preferredLanguages,
SubtitlePlaybackMode mode,
string audioTrackLanguage)
{
- var languages = preferredLanguages.ToList();
- streams = GetSortedStreams(streams, MediaStreamType.Subtitle, languages).ToList();
+ streams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages).ToList();
var full = streams.Where(s => !s.IsForced);
- var forced = streams.Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase));
MediaStream stream = null;
@@ -54,9 +52,9 @@ namespace MediaBrowser.Controller.MediaEncoding
if (mode == SubtitlePlaybackMode.Default)
{
// if the audio language is not understood by the user, load their preferred subs, if there are any
- if (!ContainsOrdinal(languages, audioTrackLanguage))
+ if (!ContainsOrdinal(preferredLanguages, audioTrackLanguage))
{
- stream = full.FirstOrDefault(s => ContainsOrdinal(languages, s.Language));
+ stream = full.FirstOrDefault(s => ContainsOrdinal(preferredLanguages, s.Language));
}
}
else if (mode == SubtitlePlaybackMode.Always)
@@ -66,7 +64,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// load forced subs if we have found no suitable full subtitles
- stream = stream ?? forced.FirstOrDefault();
+ stream = stream ?? streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase));
if (stream != null)
{
diff --git a/MediaBrowser.Controller/MediaEncoding/VideoEncodingOptions.cs b/MediaBrowser.Controller/MediaEncoding/VideoEncodingOptions.cs
deleted file mode 100644
index 773f0ea46..000000000
--- a/MediaBrowser.Controller/MediaEncoding/VideoEncodingOptions.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-
-namespace MediaBrowser.Controller.MediaEncoding
-{
- public class VideoEncodingOptions : EncodingOptions
- {
- public string VideoCodec { get; set; }
-
- public string VideoProfile { get; set; }
-
- public double? VideoLevel { get; set; }
-
- public int? VideoStreamIndex { get; set; }
-
- public int? AudioStreamIndex { get; set; }
-
- public int? SubtitleStreamIndex { get; set; }
-
- public int? MaxWidth { get; set; }
-
- public int? MaxHeight { get; set; }
-
- public int? Height { get; set; }
-
- public int? Width { get; set; }
- }
-}
diff --git a/MediaBrowser.Common/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
index a2af3707b..f1e371c1a 100644
--- a/MediaBrowser.Common/Net/BasePeriodicWebSocketListener.cs
+++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Logging;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using System;
using System.Collections.Generic;
@@ -7,7 +8,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-namespace MediaBrowser.Common.Net
+namespace MediaBrowser.Controller.Net
{
/// <summary>
/// Starts sending data over a web socket periodically when a message is received, and then stops when a corresponding stop message is received
diff --git a/MediaBrowser.Common/Net/IWebSocket.cs b/MediaBrowser.Controller/Net/IWebSocket.cs
index b31a95319..b88f2c389 100644
--- a/MediaBrowser.Common/Net/IWebSocket.cs
+++ b/MediaBrowser.Controller/Net/IWebSocket.cs
@@ -3,7 +3,7 @@ using System;
using System.Threading;
using System.Threading.Tasks;
-namespace MediaBrowser.Common.Net
+namespace MediaBrowser.Controller.Net
{
/// <summary>
/// Interface IWebSocket
diff --git a/MediaBrowser.Common/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs
index b7715e20b..37fd6708d 100644
--- a/MediaBrowser.Common/Net/IWebSocketConnection.cs
+++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs
@@ -3,7 +3,7 @@ using System;
using System.Threading;
using System.Threading.Tasks;
-namespace MediaBrowser.Common.Net
+namespace MediaBrowser.Controller.Net
{
public interface IWebSocketConnection : IDisposable
{
diff --git a/MediaBrowser.Common/Net/IWebSocketListener.cs b/MediaBrowser.Controller/Net/IWebSocketListener.cs
index 4b6c4111d..2b4fc7676 100644
--- a/MediaBrowser.Common/Net/IWebSocketListener.cs
+++ b/MediaBrowser.Controller/Net/IWebSocketListener.cs
@@ -1,6 +1,7 @@
-using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
+using System.Threading.Tasks;
-namespace MediaBrowser.Common.Net
+namespace MediaBrowser.Controller.Net
{
/// <summary>
///This is an interface for listening to messages coming through a web socket connection
diff --git a/MediaBrowser.Common/Net/WebSocketConnectEventArgs.cs b/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs
index ce22c9520..394fcd92f 100644
--- a/MediaBrowser.Common/Net/WebSocketConnectEventArgs.cs
+++ b/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs
@@ -1,6 +1,6 @@
using System;
-namespace MediaBrowser.Common.Net
+namespace MediaBrowser.Controller.Net
{
/// <summary>
/// Class WebSocketConnectEventArgs
diff --git a/MediaBrowser.Common/Net/WebSocketMessageInfo.cs b/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs
index c1f935a7b..332f16420 100644
--- a/MediaBrowser.Common/Net/WebSocketMessageInfo.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs
@@ -1,6 +1,6 @@
using MediaBrowser.Model.Net;
-namespace MediaBrowser.Common.Net
+namespace MediaBrowser.Controller.Net
{
/// <summary>
/// Class WebSocketMessageInfo
diff --git a/MediaBrowser.Controller/Providers/DirectoryService.cs b/MediaBrowser.Controller/Providers/DirectoryService.cs
index 74a9c6606..06ea7be02 100644
--- a/MediaBrowser.Controller/Providers/DirectoryService.cs
+++ b/MediaBrowser.Controller/Providers/DirectoryService.cs
@@ -46,6 +46,11 @@ namespace MediaBrowser.Controller.Providers
private Dictionary<string, FileSystemInfo> GetFileSystemDictionary(string path, bool clearCache)
{
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ throw new ArgumentNullException("path");
+ }
+
Dictionary<string, FileSystemInfo> entries;
if (clearCache)
diff --git a/MediaBrowser.Controller/Sync/ISyncManager.cs b/MediaBrowser.Controller/Sync/ISyncManager.cs
index 47339f677..59136c0e6 100644
--- a/MediaBrowser.Controller/Sync/ISyncManager.cs
+++ b/MediaBrowser.Controller/Sync/ISyncManager.cs
@@ -2,6 +2,7 @@
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Sync;
+using MediaBrowser.Model.Users;
using System.Collections.Generic;
using System.Threading.Tasks;
@@ -20,7 +21,7 @@ namespace MediaBrowser.Controller.Sync
/// Gets the jobs.
/// </summary>
/// <returns>QueryResult&lt;SyncJob&gt;.</returns>
- QueryResult<SyncJob> GetJobs(SyncJobQuery query);
+ Task<QueryResult<SyncJob>> GetJobs(SyncJobQuery query);
/// <summary>
/// Gets the job items.
@@ -37,6 +38,13 @@ namespace MediaBrowser.Controller.Sync
SyncJob GetJob(string id);
/// <summary>
+ /// Updates the job.
+ /// </summary>
+ /// <param name="job">The job.</param>
+ /// <returns>Task.</returns>
+ Task UpdateJob(SyncJob job);
+
+ /// <summary>
/// Cancels the job.
/// </summary>
/// <param name="id">The identifier.</param>
@@ -80,5 +88,26 @@ namespace MediaBrowser.Controller.Sync
/// <param name="id">The identifier.</param>
/// <returns>SyncJobItem.</returns>
SyncJobItem GetJobItem(string id);
+
+ /// <summary>
+ /// Reports the offline action.
+ /// </summary>
+ /// <param name="action">The action.</param>
+ /// <returns>Task.</returns>
+ Task ReportOfflineAction(UserAction action);
+
+ /// <summary>
+ /// Gets the ready synchronize items.
+ /// </summary>
+ /// <param name="targetId">The target identifier.</param>
+ /// <returns>List&lt;SyncedItem&gt;.</returns>
+ List<SyncedItem> GetReadySyncItems(string targetId);
+
+ /// <summary>
+ /// Synchronizes the data.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <returns>Task&lt;SyncDataResponse&gt;.</returns>
+ Task<SyncDataResponse> SyncData(SyncDataRequest request);
}
}
diff --git a/MediaBrowser.Controller/Sync/ISyncProvider.cs b/MediaBrowser.Controller/Sync/ISyncProvider.cs
index 89f61b80e..af08edb5e 100644
--- a/MediaBrowser.Controller/Sync/ISyncProvider.cs
+++ b/MediaBrowser.Controller/Sync/ISyncProvider.cs
@@ -19,10 +19,22 @@ namespace MediaBrowser.Controller.Sync
IEnumerable<SyncTarget> GetSyncTargets();
/// <summary>
+ /// Gets the synchronize targets.
+ /// </summary>
+ /// <param name="userId">The user identifier.</param>
+ /// <returns>IEnumerable&lt;SyncTarget&gt;.</returns>
+ IEnumerable<SyncTarget> GetSyncTargets(string userId);
+
+ /// <summary>
/// Gets the device profile.
/// </summary>
/// <param name="target">The target.</param>
/// <returns>DeviceProfile.</returns>
DeviceProfile GetDeviceProfile(SyncTarget target);
}
+
+ public interface IHasUniqueTargetIds
+ {
+
+ }
}
diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs
index bdb1c4cbf..d1941c856 100644
--- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs
+++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs
@@ -18,6 +18,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml;
+using MediaBrowser.Model.Net;
namespace MediaBrowser.Dlna.Didl
{
diff --git a/MediaBrowser.Dlna/Profiles/Windows81Profile.cs b/MediaBrowser.Dlna/Profiles/Windows81Profile.cs
index 1d3f5046c..cf7790c52 100644
--- a/MediaBrowser.Dlna/Profiles/Windows81Profile.cs
+++ b/MediaBrowser.Dlna/Profiles/Windows81Profile.cs
@@ -22,7 +22,15 @@ namespace MediaBrowser.Dlna.Profiles
{
Container = "mp3",
AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
+ Type = DlnaProfileType.Audio,
+ Context = EncodingContext.Streaming
+ },
+ new TranscodingProfile
+ {
+ Container = "mp3",
+ AudioCodec = "mp3",
+ Type = DlnaProfileType.Audio,
+ Context = EncodingContext.Static
},
new TranscodingProfile
{
@@ -101,6 +109,14 @@ namespace MediaBrowser.Dlna.Profiles
new DirectPlayProfile
{
+ Container = "m4a",
+ AudioCodec = "aac",
+ VideoCodec = "",
+ Type = DlnaProfileType.Audio
+ },
+
+ new DirectPlayProfile
+ {
Container = "jpeg",
Type = DlnaProfileType.Photo
}
@@ -111,21 +127,6 @@ namespace MediaBrowser.Dlna.Profiles
new CodecProfile
{
Type = CodecType.Video,
- Conditions = new []
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- Property = ProfileConditionValue.VideoBitDepth,
- Value = "8",
- IsRequired = false
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.Video,
Codec="h264",
Conditions = new []
{
@@ -148,6 +149,28 @@ namespace MediaBrowser.Dlna.Profiles
Condition = ProfileConditionType.LessThanEqual,
Property = ProfileConditionValue.VideoLevel,
Value = "51"
+ },
+ new ProfileCondition
+ {
+ Condition = ProfileConditionType.LessThanEqual,
+ Property = ProfileConditionValue.VideoBitDepth,
+ Value = "8",
+ IsRequired = false
+ }
+ }
+ },
+
+ new CodecProfile
+ {
+ Type = CodecType.Video,
+ Conditions = new []
+ {
+ new ProfileCondition
+ {
+ Condition = ProfileConditionType.LessThanEqual,
+ Property = ProfileConditionValue.VideoBitDepth,
+ Value = "8",
+ IsRequired = false
}
}
},
diff --git a/MediaBrowser.Dlna/Profiles/WindowsPhoneProfile.cs b/MediaBrowser.Dlna/Profiles/WindowsPhoneProfile.cs
index 3fb40dedd..3a0ec1576 100644
--- a/MediaBrowser.Dlna/Profiles/WindowsPhoneProfile.cs
+++ b/MediaBrowser.Dlna/Profiles/WindowsPhoneProfile.cs
@@ -17,7 +17,15 @@ namespace MediaBrowser.Dlna.Profiles
{
Container = "mp3",
AudioCodec = "mp3",
- Type = DlnaProfileType.Audio
+ Type = DlnaProfileType.Audio,
+ Context = EncodingContext.Streaming
+ },
+ new TranscodingProfile
+ {
+ Container = "mp3",
+ AudioCodec = "mp3",
+ Type = DlnaProfileType.Audio,
+ Context = EncodingContext.Static
},
new TranscodingProfile
{
@@ -105,20 +113,6 @@ namespace MediaBrowser.Dlna.Profiles
new CodecProfile
{
Type = CodecType.Video,
- Conditions = new []
- {
- new ProfileCondition
- {
- Condition = ProfileConditionType.NotEquals,
- Property = ProfileConditionValue.IsAnamorphic,
- Value = "true"
- }
- }
- },
-
- new CodecProfile
- {
- Type = CodecType.Video,
Codec="h264",
Conditions = new []
{
@@ -154,7 +148,13 @@ namespace MediaBrowser.Dlna.Profiles
Property = ProfileConditionValue.VideoLevel,
Value = "3"
},
- new ProfileCondition(ProfileConditionType.EqualsAny, ProfileConditionValue.VideoProfile, "baseline|constrained baseline")
+ new ProfileCondition(ProfileConditionType.EqualsAny, ProfileConditionValue.VideoProfile, "baseline|constrained baseline"),
+ new ProfileCondition
+ {
+ Condition = ProfileConditionType.NotEquals,
+ Property = ProfileConditionValue.IsAnamorphic,
+ Value = "true"
+ }
}
},
@@ -188,6 +188,12 @@ namespace MediaBrowser.Dlna.Profiles
Property = ProfileConditionValue.VideoFramerate,
Value = "24",
IsRequired = false
+ },
+ new ProfileCondition
+ {
+ Condition = ProfileConditionType.NotEquals,
+ Property = ProfileConditionValue.IsAnamorphic,
+ Value = "true"
}
}
},
diff --git a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs
index 6f8047e4c..74e3b61ca 100644
--- a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs
+++ b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs
@@ -28,8 +28,6 @@ namespace MediaBrowser.LocalMetadata
var path = file.FullName;
- //await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
try
{
result.Item = new T();
@@ -45,10 +43,6 @@ namespace MediaBrowser.LocalMetadata
{
result.HasMetadata = false;
}
- finally
- {
- //XmlProviderUtils.XmlParsingResourcePool.Release();
- }
return result;
}
@@ -90,7 +84,7 @@ namespace MediaBrowser.LocalMetadata
{
get
{
- return "Media Browser Legacy Xml";
+ return "Media Browser Xml";
}
}
diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
index 044d29a1b..7f83aa61d 100644
--- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
+++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
@@ -1,7 +1,6 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
@@ -76,11 +75,14 @@ namespace MediaBrowser.LocalMetadata.Images
{
return directoryService.GetFileSystemEntries(path)
.Where(i => BaseItem.SupportedImageExtensions.Contains(i.Extension, StringComparer.OrdinalIgnoreCase) ||
- (i.Attributes & FileAttributes.Directory) == FileAttributes.Directory);
+ (i.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
+
+ .OrderBy(i => BaseItem.SupportedImageExtensionsList.IndexOf(i.Extension ?? string.Empty));
}
return directoryService.GetFiles(path)
- .Where(i => BaseItem.SupportedImageExtensions.Contains(i.Extension, StringComparer.OrdinalIgnoreCase));
+ .Where(i => BaseItem.SupportedImageExtensions.Contains(i.Extension, StringComparer.OrdinalIgnoreCase))
+ .OrderBy(i => BaseItem.SupportedImageExtensionsList.IndexOf(i.Extension ?? string.Empty));
}
public List<LocalImageInfo> GetImages(IHasImages item, IDirectoryService directoryService)
@@ -109,6 +111,7 @@ namespace MediaBrowser.LocalMetadata.Images
return !string.IsNullOrEmpty(ext) &&
BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
})
+ .OrderBy(i => BaseItem.SupportedImageExtensionsList.IndexOf(i.Extension ?? string.Empty))
.ToList();
var list = new List<LocalImageInfo>();
@@ -166,8 +169,8 @@ namespace MediaBrowser.LocalMetadata.Images
var names = new List<string>
{
"folder",
- "cover",
"poster",
+ "cover",
"default"
};
@@ -402,13 +405,7 @@ namespace MediaBrowser.LocalMetadata.Images
private FileSystemInfo GetImage(IEnumerable<FileSystemInfo> files, string name)
{
- var candidates = files
- .Where(i => string.Equals(name, _fileSystem.GetFileNameWithoutExtension(i), StringComparison.OrdinalIgnoreCase))
- .ToList();
-
- return BaseItem.SupportedImageExtensions
- .Select(i => candidates.FirstOrDefault(c => string.Equals(c.Extension, i, StringComparison.OrdinalIgnoreCase)))
- .FirstOrDefault(i => i != null);
+ return files.FirstOrDefault(i => ((i.Attributes & FileAttributes.Directory) != FileAttributes.Directory) && string.Equals(name, _fileSystem.GetFileNameWithoutExtension(i), StringComparison.OrdinalIgnoreCase));
}
}
}
diff --git a/MediaBrowser.LocalMetadata/Savers/FolderXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/FolderXmlSaver.cs
index fa3d7d87d..67fa12b55 100644
--- a/MediaBrowser.LocalMetadata/Savers/FolderXmlSaver.cs
+++ b/MediaBrowser.LocalMetadata/Savers/FolderXmlSaver.cs
@@ -49,7 +49,7 @@ namespace MediaBrowser.LocalMetadata.Savers
!(item is GameSystem) &&
!(item is Playlist))
{
- return updateType >= ItemUpdateType.MetadataDownload;
+ return updateType >= ItemUpdateType.MetadataEdit;
}
}
diff --git a/MediaBrowser.LocalMetadata/Savers/MovieXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/MovieXmlSaver.cs
index 6fe9f88f0..cad08e638 100644
--- a/MediaBrowser.LocalMetadata/Savers/MovieXmlSaver.cs
+++ b/MediaBrowser.LocalMetadata/Savers/MovieXmlSaver.cs
@@ -19,11 +19,13 @@ namespace MediaBrowser.LocalMetadata.Savers
{
private readonly IItemRepository _itemRepository;
private readonly IServerConfigurationManager _config;
+ private readonly ILibraryManager _libraryManager;
- public MovieXmlSaver(IItemRepository itemRepository, IServerConfigurationManager config)
+ public MovieXmlSaver(IItemRepository itemRepository, IServerConfigurationManager config, ILibraryManager libraryManager)
{
_itemRepository = itemRepository;
_config = config;
+ _libraryManager = libraryManager;
}
public string Name
@@ -52,6 +54,15 @@ namespace MediaBrowser.LocalMetadata.Savers
// Check parent for null to avoid running this against things like video backdrops
if (video != null && !(item is Episode) && !video.IsOwnedItem)
{
+ // If it's a plain video, skip if content type is unset (unless editing)
+ if (video.GetType() == typeof(Video))
+ {
+ if (updateType < ItemUpdateType.MetadataEdit && string.IsNullOrEmpty(_libraryManager.GetContentType(video)))
+ {
+ return false;
+ }
+ }
+
return updateType >= ItemUpdateType.MetadataDownload;
}
diff --git a/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs
index 3e11c994b..b5b9278cc 100644
--- a/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs
+++ b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs
@@ -112,7 +112,8 @@ namespace MediaBrowser.LocalMetadata.Savers
"Website",
"Zap2ItId",
"CollectionItems",
- "PlaylistItems"
+ "PlaylistItems",
+ "Shares"
}.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
@@ -649,7 +650,7 @@ namespace MediaBrowser.LocalMetadata.Savers
var hasShares = item as IHasShares;
if (hasShares != null)
{
-
+ AddShares(hasShares, builder);
}
}
diff --git a/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs b/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs
new file mode 100644
index 000000000..17470d206
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs
@@ -0,0 +1,45 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Configuration;
+using System.Collections.Generic;
+using System.IO;
+
+namespace MediaBrowser.MediaEncoding.Configuration
+{
+ public class EncodingConfigurationFactory : IConfigurationFactory
+ {
+ public IEnumerable<ConfigurationStore> GetConfigurations()
+ {
+ return new[]
+ {
+ new EncodingConfigurationStore()
+ };
+ }
+ }
+
+ public class EncodingConfigurationStore : ConfigurationStore, IValidatingConfiguration
+ {
+ public EncodingConfigurationStore()
+ {
+ ConfigurationType = typeof(EncodingOptions);
+ Key = "encoding";
+ }
+
+ public void Validate(object oldConfig, object newConfig)
+ {
+ var oldEncodingConfig = (EncodingOptions)oldConfig;
+ var newEncodingConfig = (EncodingOptions)newConfig;
+
+ var newPath = newEncodingConfig.TranscodingTempPath;
+
+ if (!string.IsNullOrWhiteSpace(newPath)
+ && !string.Equals(oldEncodingConfig.TranscodingTempPath ?? string.Empty, newPath))
+ {
+ // Validate
+ if (!Directory.Exists(newPath))
+ {
+ throw new DirectoryNotFoundException(string.Format("{0} does not exist.", newPath));
+ }
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
new file mode 100644
index 000000000..7054accfa
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
@@ -0,0 +1,86 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Channels;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using System;
+using System.Collections.Generic;
+
+namespace MediaBrowser.MediaEncoding.Encoder
+{
+ public class AudioEncoder : BaseEncoder
+ {
+ public AudioEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder) : base(mediaEncoder, logger, configurationManager, fileSystem, liveTvManager, isoManager, libraryManager, channelManager, sessionManager, subtitleEncoder)
+ {
+ }
+
+ protected override string GetCommandLineArguments(EncodingJob job)
+ {
+ var audioTranscodeParams = new List<string>();
+
+ var bitrate = job.OutputAudioBitrate;
+
+ if (bitrate.HasValue)
+ {
+ audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(UsCulture));
+ }
+
+ if (job.OutputAudioChannels.HasValue)
+ {
+ audioTranscodeParams.Add("-ac " + job.OutputAudioChannels.Value.ToString(UsCulture));
+ }
+
+ if (job.OutputAudioSampleRate.HasValue)
+ {
+ audioTranscodeParams.Add("-ar " + job.OutputAudioSampleRate.Value.ToString(UsCulture));
+ }
+
+ var threads = GetNumberOfThreads(job, false);
+
+ var inputModifier = GetInputModifier(job);
+
+ return string.Format("{0} {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
+ inputModifier,
+ GetInputArgument(job),
+ threads,
+ " -vn",
+ string.Join(" ", audioTranscodeParams.ToArray()),
+ job.OutputFilePath).Trim();
+ }
+
+ protected override string GetOutputFileExtension(EncodingJob state)
+ {
+ var ext = base.GetOutputFileExtension(state);
+
+ if (!string.IsNullOrEmpty(ext))
+ {
+ return ext;
+ }
+
+ var audioCodec = state.Options.AudioCodec;
+
+ if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase))
+ {
+ return ".aac";
+ }
+ if (string.Equals("mp3", audioCodec, StringComparison.OrdinalIgnoreCase))
+ {
+ return ".mp3";
+ }
+ if (string.Equals("vorbis", audioCodec, StringComparison.OrdinalIgnoreCase))
+ {
+ return ".ogg";
+ }
+ if (string.Equals("wma", audioCodec, StringComparison.OrdinalIgnoreCase))
+ {
+ return ".wma";
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
new file mode 100644
index 000000000..a350d0577
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
@@ -0,0 +1,1049 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Channels;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.MediaEncoding.Subtitles;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Drawing;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.MediaEncoding.Encoder
+{
+ public abstract class BaseEncoder
+ {
+ protected readonly MediaEncoder MediaEncoder;
+ protected readonly ILogger Logger;
+ protected readonly IServerConfigurationManager ConfigurationManager;
+ protected readonly IFileSystem FileSystem;
+ protected readonly ILiveTvManager LiveTvManager;
+ protected readonly IIsoManager IsoManager;
+ protected readonly ILibraryManager LibraryManager;
+ protected readonly IChannelManager ChannelManager;
+ protected readonly ISessionManager SessionManager;
+ protected readonly ISubtitleEncoder SubtitleEncoder;
+
+ protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
+
+ public BaseEncoder(MediaEncoder mediaEncoder,
+ ILogger logger,
+ IServerConfigurationManager configurationManager,
+ IFileSystem fileSystem,
+ ILiveTvManager liveTvManager,
+ IIsoManager isoManager,
+ ILibraryManager libraryManager,
+ IChannelManager channelManager,
+ ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder)
+ {
+ MediaEncoder = mediaEncoder;
+ Logger = logger;
+ ConfigurationManager = configurationManager;
+ FileSystem = fileSystem;
+ LiveTvManager = liveTvManager;
+ IsoManager = isoManager;
+ LibraryManager = libraryManager;
+ ChannelManager = channelManager;
+ SessionManager = sessionManager;
+ SubtitleEncoder = subtitleEncoder;
+ }
+
+ public async Task<EncodingJob> Start(EncodingJobOptions options,
+ IProgress<double> progress,
+ CancellationToken cancellationToken)
+ {
+ var encodingJob = await new EncodingJobFactory(Logger, LiveTvManager, LibraryManager, ChannelManager)
+ .CreateJob(options, IsVideoEncoder, progress, cancellationToken).ConfigureAwait(false);
+
+ encodingJob.OutputFilePath = GetOutputFilePath(encodingJob);
+ Directory.CreateDirectory(Path.GetDirectoryName(encodingJob.OutputFilePath));
+
+ if (options.Context == EncodingContext.Static && encodingJob.IsInputVideo)
+ {
+ encodingJob.ReadInputAtNativeFramerate = true;
+ }
+
+ await AcquireResources(encodingJob, cancellationToken).ConfigureAwait(false);
+
+ var commandLineArgs = GetCommandLineArguments(encodingJob);
+
+ if (GetEncodingOptions().EnableDebugLogging)
+ {
+ commandLineArgs = "-loglevel debug " + commandLineArgs;
+ }
+
+ var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ CreateNoWindow = true,
+ UseShellExecute = false,
+
+ // Must consume both stdout and stderr or deadlocks may occur
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ RedirectStandardInput = true,
+
+ FileName = MediaEncoder.EncoderPath,
+ Arguments = commandLineArgs,
+
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false
+ },
+
+ EnableRaisingEvents = true
+ };
+
+ var workingDirectory = GetWorkingDirectory(options);
+ if (!string.IsNullOrWhiteSpace(workingDirectory))
+ {
+ process.StartInfo.WorkingDirectory = workingDirectory;
+ }
+
+ OnTranscodeBeginning(encodingJob);
+
+ var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
+ Logger.Info(commandLineLogMessage);
+
+ var logFilePath = Path.Combine(ConfigurationManager.CommonApplicationPaths.LogDirectoryPath, "transcode-" + Guid.NewGuid() + ".txt");
+ Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
+
+ // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
+ encodingJob.LogFileStream = FileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true);
+
+ var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(commandLineLogMessage + Environment.NewLine + Environment.NewLine);
+ await encodingJob.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationToken).ConfigureAwait(false);
+
+ process.Exited += (sender, args) => OnFfMpegProcessExited(process, encodingJob);
+
+ try
+ {
+ process.Start();
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error starting ffmpeg", ex);
+
+ OnTranscodeFailedToStart(encodingJob.OutputFilePath, encodingJob);
+
+ throw;
+ }
+
+ // MUST read both stdout and stderr asynchronously or a deadlock may occurr
+ process.BeginOutputReadLine();
+
+ // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
+ new JobLogger(Logger).StartStreamingLog(encodingJob, process.StandardError.BaseStream, encodingJob.LogFileStream);
+
+ // Wait for the file to exist before proceeeding
+ while (!File.Exists(encodingJob.OutputFilePath) && !encodingJob.HasExited)
+ {
+ await Task.Delay(100, cancellationToken).ConfigureAwait(false);
+ }
+
+ return encodingJob;
+ }
+
+ /// <summary>
+ /// Processes the exited.
+ /// </summary>
+ /// <param name="process">The process.</param>
+ /// <param name="job">The job.</param>
+ private void OnFfMpegProcessExited(Process process, EncodingJob job)
+ {
+ job.HasExited = true;
+
+ Logger.Debug("Disposing stream resources");
+ job.Dispose();
+
+ try
+ {
+ Logger.Info("FFMpeg exited with code {0}", process.ExitCode);
+
+ try
+ {
+ job.TaskCompletionSource.TrySetResult(true);
+ }
+ catch
+ {
+ }
+ }
+ catch
+ {
+ Logger.Error("FFMpeg exited with an error.");
+
+ try
+ {
+ job.TaskCompletionSource.TrySetException(new ApplicationException());
+ }
+ catch
+ {
+ }
+ }
+
+ // This causes on exited to be called twice:
+ //try
+ //{
+ // // Dispose the process
+ // process.Dispose();
+ //}
+ //catch (Exception ex)
+ //{
+ // Logger.ErrorException("Error disposing ffmpeg.", ex);
+ //}
+ }
+
+ private void OnTranscodeBeginning(EncodingJob job)
+ {
+ job.ReportTranscodingProgress(null, null, null, null);
+ }
+
+ private void OnTranscodeFailedToStart(string path, EncodingJob job)
+ {
+ if (!string.IsNullOrWhiteSpace(job.Options.DeviceId))
+ {
+ SessionManager.ClearTranscodingInfo(job.Options.DeviceId);
+ }
+ }
+
+ protected virtual bool IsVideoEncoder
+ {
+ get { return false; }
+ }
+
+ protected virtual string GetWorkingDirectory(EncodingJobOptions options)
+ {
+ return null;
+ }
+
+ protected EncodingOptions GetEncodingOptions()
+ {
+ return ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
+ }
+
+ protected abstract string GetCommandLineArguments(EncodingJob job);
+
+ private string GetOutputFilePath(EncodingJob state)
+ {
+ var folder = ConfigurationManager.ApplicationPaths.TranscodingTempPath;
+
+ var outputFileExtension = GetOutputFileExtension(state);
+ var context = state.Options.Context;
+
+ var filename = state.Id + (outputFileExtension ?? string.Empty).ToLower();
+ return Path.Combine(folder, context.ToString().ToLower(), filename);
+ }
+
+ protected virtual string GetOutputFileExtension(EncodingJob state)
+ {
+ if (!string.IsNullOrWhiteSpace(state.Options.OutputContainer))
+ {
+ return "." + state.Options.OutputContainer;
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Gets the number of threads.
+ /// </summary>
+ /// <returns>System.Int32.</returns>
+ protected int GetNumberOfThreads(EncodingJob job, bool isWebm)
+ {
+ if (isWebm)
+ {
+ // Recommended per docs
+ return Math.Max(Environment.ProcessorCount - 1, 2);
+ }
+
+ // Use more when this is true. -re will keep cpu usage under control
+ if (job.ReadInputAtNativeFramerate)
+ {
+ if (isWebm)
+ {
+ return Math.Max(Environment.ProcessorCount - 1, 2);
+ }
+
+ return 0;
+ }
+
+ // Webm: http://www.webmproject.org/docs/encoder-parameters/
+ // The decoder will usually automatically use an appropriate number of threads according to how many cores are available but it can only use multiple threads
+ // for the coefficient data if the encoder selected --token-parts > 0 at encode time.
+
+ switch (GetQualitySetting())
+ {
+ case EncodingQuality.HighSpeed:
+ return 2;
+ case EncodingQuality.HighQuality:
+ return 2;
+ case EncodingQuality.MaxQuality:
+ return isWebm ? Math.Max(Environment.ProcessorCount - 1, 2) : 0;
+ default:
+ throw new Exception("Unrecognized MediaEncodingQuality value.");
+ }
+ }
+
+ protected EncodingQuality GetQualitySetting()
+ {
+ var quality = GetEncodingOptions().EncodingQuality;
+
+ if (quality == EncodingQuality.Auto)
+ {
+ var cpuCount = Environment.ProcessorCount;
+
+ if (cpuCount >= 4)
+ {
+ //return EncodingQuality.HighQuality;
+ }
+
+ return EncodingQuality.HighSpeed;
+ }
+
+ return quality;
+ }
+
+ protected string GetInputModifier(EncodingJob job, bool genPts = true)
+ {
+ var inputModifier = string.Empty;
+
+ var probeSize = GetProbeSizeArgument(job);
+ inputModifier += " " + probeSize;
+ inputModifier = inputModifier.Trim();
+
+ var userAgentParam = GetUserAgentParam(job);
+
+ if (!string.IsNullOrWhiteSpace(userAgentParam))
+ {
+ inputModifier += " " + userAgentParam;
+ }
+
+ inputModifier = inputModifier.Trim();
+
+ inputModifier += " " + GetFastSeekCommandLineParameter(job.Options);
+ inputModifier = inputModifier.Trim();
+
+ if (job.IsVideoRequest && genPts)
+ {
+ inputModifier += " -fflags +genpts";
+ }
+
+ if (!string.IsNullOrEmpty(job.InputAudioSync))
+ {
+ inputModifier += " -async " + job.InputAudioSync;
+ }
+
+ if (!string.IsNullOrEmpty(job.InputVideoSync))
+ {
+ inputModifier += " -vsync " + job.InputVideoSync;
+ }
+
+ if (job.ReadInputAtNativeFramerate)
+ {
+ inputModifier += " -re";
+ }
+
+ return inputModifier;
+ }
+
+ private string GetUserAgentParam(EncodingJob job)
+ {
+ string useragent = null;
+
+ job.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent);
+
+ if (!string.IsNullOrWhiteSpace(useragent))
+ {
+ return "-user-agent \"" + useragent + "\"";
+ }
+
+ return string.Empty;
+ }
+
+ /// <summary>
+ /// Gets the probe size argument.
+ /// </summary>
+ /// <param name="job">The job.</param>
+ /// <returns>System.String.</returns>
+ private string GetProbeSizeArgument(EncodingJob job)
+ {
+ if (job.PlayableStreamFileNames.Count > 0)
+ {
+ return MediaEncoder.GetProbeSizeArgument(job.PlayableStreamFileNames.ToArray(), job.InputProtocol);
+ }
+
+ return MediaEncoder.GetProbeSizeArgument(new[] { job.MediaPath }, job.InputProtocol);
+ }
+
+ /// <summary>
+ /// Gets the fast seek command line parameter.
+ /// </summary>
+ /// <param name="options">The options.</param>
+ /// <returns>System.String.</returns>
+ /// <value>The fast seek command line parameter.</value>
+ protected string GetFastSeekCommandLineParameter(EncodingJobOptions options)
+ {
+ var time = options.StartTimeTicks;
+
+ if (time.HasValue && time.Value > 0)
+ {
+ return string.Format("-ss {0}", MediaEncoder.GetTimeParameter(time.Value));
+ }
+
+ return string.Empty;
+ }
+
+ /// <summary>
+ /// Gets the input argument.
+ /// </summary>
+ /// <param name="job">The job.</param>
+ /// <returns>System.String.</returns>
+ protected string GetInputArgument(EncodingJob job)
+ {
+ var arg = "-i " + GetInputPathArgument(job);
+
+ if (job.SubtitleStream != null)
+ {
+ if (job.SubtitleStream.IsExternal && !job.SubtitleStream.IsTextSubtitleStream)
+ {
+ arg += " -i " + job.SubtitleStream.Path;
+ }
+ }
+
+ return arg;
+ }
+
+ private string GetInputPathArgument(EncodingJob job)
+ {
+ //if (job.InputProtocol == MediaProtocol.File &&
+ // job.RunTimeTicks.HasValue &&
+ // job.VideoType == VideoType.VideoFile &&
+ // !string.Equals(job.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ //{
+ // if (job.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && job.IsInputVideo)
+ // {
+ // if (SupportsThrottleWithStream)
+ // {
+ // var url = "http://localhost:" + ServerConfigurationManager.Configuration.HttpServerPortNumber.ToString(UsCulture) + "/mediabrowser/videos/" + job.Request.Id + "/stream?static=true&Throttle=true&mediaSourceId=" + job.Request.MediaSourceId;
+
+ // url += "&transcodingJobId=" + transcodingJobId;
+
+ // return string.Format("\"{0}\"", url);
+ // }
+ // }
+ //}
+
+ var protocol = job.InputProtocol;
+
+ var inputPath = new[] { job.MediaPath };
+
+ if (job.IsInputVideo)
+ {
+ if (!(job.VideoType == VideoType.Iso && job.IsoMount == null))
+ {
+ inputPath = MediaEncoderHelpers.GetInputArgument(job.MediaPath, job.InputProtocol, job.IsoMount, job.PlayableStreamFileNames);
+ }
+ }
+
+ return MediaEncoder.GetInputArgument(inputPath, protocol);
+ }
+
+ private async Task AcquireResources(EncodingJob state, CancellationToken cancellationToken)
+ {
+ if (state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
+ {
+ state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationToken).ConfigureAwait(false);
+ }
+
+ if (string.IsNullOrEmpty(state.MediaPath))
+ {
+ var checkCodecs = false;
+
+ if (string.Equals(state.ItemType, typeof(LiveTvChannel).Name))
+ {
+ var streamInfo = await LiveTvManager.GetChannelStream(state.Options.ItemId, cancellationToken).ConfigureAwait(false);
+
+ state.LiveTvStreamId = streamInfo.Id;
+
+ state.MediaPath = streamInfo.Path;
+ state.InputProtocol = streamInfo.Protocol;
+
+ await Task.Delay(1500, cancellationToken).ConfigureAwait(false);
+
+ AttachMediaStreamInfo(state, streamInfo, state.Options);
+ checkCodecs = true;
+ }
+
+ else if (string.Equals(state.ItemType, typeof(LiveTvVideoRecording).Name) ||
+ string.Equals(state.ItemType, typeof(LiveTvAudioRecording).Name))
+ {
+ var streamInfo = await LiveTvManager.GetRecordingStream(state.Options.ItemId, cancellationToken).ConfigureAwait(false);
+
+ state.LiveTvStreamId = streamInfo.Id;
+
+ state.MediaPath = streamInfo.Path;
+ state.InputProtocol = streamInfo.Protocol;
+
+ await Task.Delay(1500, cancellationToken).ConfigureAwait(false);
+
+ AttachMediaStreamInfo(state, streamInfo, state.Options);
+ checkCodecs = true;
+ }
+
+ if (state.IsVideoRequest && checkCodecs)
+ {
+ if (state.VideoStream != null && EncodingJobFactory.CanStreamCopyVideo(state.Options, state.VideoStream))
+ {
+ state.OutputVideoCodec = "copy";
+ }
+
+ if (state.AudioStream != null && EncodingJobFactory.CanStreamCopyAudio(state.Options, state.AudioStream, state.SupportedAudioCodecs))
+ {
+ state.OutputAudioCodec = "copy";
+ }
+ }
+ }
+ }
+
+ private void AttachMediaStreamInfo(EncodingJob state,
+ ChannelMediaInfo mediaInfo,
+ EncodingJobOptions videoRequest)
+ {
+ var mediaSource = mediaInfo.ToMediaSource();
+
+ state.InputProtocol = mediaSource.Protocol;
+ state.MediaPath = mediaSource.Path;
+ state.RunTimeTicks = mediaSource.RunTimeTicks;
+ state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
+ state.InputBitrate = mediaSource.Bitrate;
+ state.InputFileSize = mediaSource.Size;
+ state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
+
+ if (state.ReadInputAtNativeFramerate)
+ {
+ state.OutputAudioSync = "1000";
+ state.InputVideoSync = "-1";
+ state.InputAudioSync = "1";
+ }
+
+ EncodingJobFactory.AttachMediaStreamInfo(state, mediaSource.MediaStreams, videoRequest);
+ }
+
+ /// <summary>
+ /// Gets the internal graphical subtitle param.
+ /// </summary>
+ /// <param name="state">The state.</param>
+ /// <param name="outputVideoCodec">The output video codec.</param>
+ /// <returns>System.String.</returns>
+ protected string GetGraphicalSubtitleParam(EncodingJob state, string outputVideoCodec)
+ {
+ var outputSizeParam = string.Empty;
+
+ var request = state.Options;
+
+ // Add resolution params, if specified
+ if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
+ {
+ outputSizeParam = GetOutputSizeParam(state, outputVideoCodec).TrimEnd('"');
+ outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
+ }
+
+ var videoSizeParam = string.Empty;
+
+ if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue)
+ {
+ videoSizeParam = string.Format(",scale={0}:{1}", state.VideoStream.Width.Value.ToString(UsCulture), state.VideoStream.Height.Value.ToString(UsCulture));
+ }
+
+ var mapPrefix = state.SubtitleStream.IsExternal ?
+ 1 :
+ 0;
+
+ var subtitleStreamIndex = state.SubtitleStream.IsExternal
+ ? 0
+ : state.SubtitleStream.Index;
+
+ return string.Format(" -filter_complex \"[{0}:{1}]format=yuva444p{4},lut=u=128:v=128:y=gammaval(.3)[sub] ; [0:{2}] [sub] overlay{3}\"",
+ mapPrefix.ToString(UsCulture),
+ subtitleStreamIndex.ToString(UsCulture),
+ state.VideoStream.Index.ToString(UsCulture),
+ outputSizeParam,
+ videoSizeParam);
+ }
+
+ /// <summary>
+ /// Gets the video bitrate to specify on the command line
+ /// </summary>
+ /// <param name="state">The state.</param>
+ /// <param name="videoCodec">The video codec.</param>
+ /// <param name="isHls">if set to <c>true</c> [is HLS].</param>
+ /// <returns>System.String.</returns>
+ protected string GetVideoQualityParam(EncodingJob state, string videoCodec, bool isHls)
+ {
+ var param = string.Empty;
+
+ var isVc1 = state.VideoStream != null &&
+ string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
+
+ var qualitySetting = GetQualitySetting();
+
+ if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
+ {
+ switch (qualitySetting)
+ {
+ case EncodingQuality.HighSpeed:
+ param = "-preset superfast";
+ break;
+ case EncodingQuality.HighQuality:
+ param = "-preset superfast";
+ break;
+ case EncodingQuality.MaxQuality:
+ param = "-preset superfast";
+ break;
+ }
+
+ switch (qualitySetting)
+ {
+ case EncodingQuality.HighSpeed:
+ param += " -crf 23";
+ break;
+ case EncodingQuality.HighQuality:
+ param += " -crf 20";
+ break;
+ case EncodingQuality.MaxQuality:
+ param += " -crf 18";
+ break;
+ }
+ }
+
+ // webm
+ else if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
+ {
+ // Values 0-3, 0 being highest quality but slower
+ var profileScore = 0;
+
+ string crf;
+ var qmin = "0";
+ var qmax = "50";
+
+ switch (qualitySetting)
+ {
+ case EncodingQuality.HighSpeed:
+ crf = "10";
+ break;
+ case EncodingQuality.HighQuality:
+ crf = "6";
+ break;
+ case EncodingQuality.MaxQuality:
+ crf = "4";
+ break;
+ default:
+ throw new ArgumentException("Unrecognized quality setting");
+ }
+
+ if (isVc1)
+ {
+ profileScore++;
+ }
+
+ // Max of 2
+ profileScore = Math.Min(profileScore, 2);
+
+ // http://www.webmproject.org/docs/encoder-parameters/
+ param = string.Format("-speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}",
+ profileScore.ToString(UsCulture),
+ crf,
+ qmin,
+ qmax);
+ }
+
+ else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase))
+ {
+ param = "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
+ }
+
+ // asf/wmv
+ else if (string.Equals(videoCodec, "wmv2", StringComparison.OrdinalIgnoreCase))
+ {
+ param = "-qmin 2";
+ }
+
+ else if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
+ {
+ param = "-mbd 2";
+ }
+
+ param += GetVideoBitrateParam(state, videoCodec, isHls);
+
+ var framerate = GetFramerateParam(state);
+ if (framerate.HasValue)
+ {
+ param += string.Format(" -r {0}", framerate.Value.ToString(UsCulture));
+ }
+
+ if (!string.IsNullOrEmpty(state.OutputVideoSync))
+ {
+ param += " -vsync " + state.OutputVideoSync;
+ }
+
+ if (!string.IsNullOrEmpty(state.Options.Profile))
+ {
+ param += " -profile:v " + state.Options.Profile;
+ }
+
+ if (state.Options.Level.HasValue)
+ {
+ param += " -level " + state.Options.Level.Value.ToString(UsCulture);
+ }
+
+ return param;
+ }
+
+ protected string GetVideoBitrateParam(EncodingJob state, string videoCodec, bool isHls)
+ {
+ var bitrate = state.OutputVideoBitrate;
+
+ if (bitrate.HasValue)
+ {
+ var hasFixedResolution = state.Options.HasFixedResolution;
+
+ if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
+ {
+ if (hasFixedResolution)
+ {
+ return string.Format(" -minrate:v ({0}*.90) -maxrate:v ({0}*1.10) -bufsize:v {0} -b:v {0}", bitrate.Value.ToString(UsCulture));
+ }
+
+ // With vpx when crf is used, b:v becomes a max rate
+ // https://trac.ffmpeg.org/wiki/vpxEncodingGuide. But higher bitrate source files -b:v causes judder so limite the bitrate but dont allow it to "saturate" the bitrate. So dont contrain it down just up.
+ return string.Format(" -maxrate:v {0} -bufsize:v ({0}*2) -b:v {0}", bitrate.Value.ToString(UsCulture));
+ }
+
+ if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
+ {
+ return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
+ }
+
+ // H264
+ if (hasFixedResolution)
+ {
+ if (isHls)
+ {
+ return string.Format(" -b:v {0} -maxrate ({0}*.80) -bufsize {0}", bitrate.Value.ToString(UsCulture));
+ }
+
+ return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
+ }
+
+ return string.Format(" -maxrate {0} -bufsize {1}",
+ bitrate.Value.ToString(UsCulture),
+ (bitrate.Value * 2).ToString(UsCulture));
+ }
+
+ return string.Empty;
+ }
+
+ protected double? GetFramerateParam(EncodingJob state)
+ {
+ if (state.Options.Framerate.HasValue)
+ {
+ return state.Options.Framerate.Value;
+ }
+
+ var maxrate = state.Options.MaxFramerate;
+
+ if (maxrate.HasValue && state.VideoStream != null)
+ {
+ var contentRate = state.VideoStream.AverageFrameRate ?? state.VideoStream.RealFrameRate;
+
+ if (contentRate.HasValue && contentRate.Value > maxrate.Value)
+ {
+ return maxrate;
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Gets the map args.
+ /// </summary>
+ /// <param name="state">The state.</param>
+ /// <returns>System.String.</returns>
+ protected virtual string GetMapArgs(EncodingJob state)
+ {
+ // If we don't have known media info
+ // If input is video, use -sn to drop subtitles
+ // Otherwise just return empty
+ if (state.VideoStream == null && state.AudioStream == null)
+ {
+ return state.IsInputVideo ? "-sn" : string.Empty;
+ }
+
+ // We have media info, but we don't know the stream indexes
+ if (state.VideoStream != null && state.VideoStream.Index == -1)
+ {
+ return "-sn";
+ }
+
+ // We have media info, but we don't know the stream indexes
+ if (state.AudioStream != null && state.AudioStream.Index == -1)
+ {
+ return state.IsInputVideo ? "-sn" : string.Empty;
+ }
+
+ var args = string.Empty;
+
+ if (state.VideoStream != null)
+ {
+ args += string.Format("-map 0:{0}", state.VideoStream.Index);
+ }
+ else
+ {
+ args += "-map -0:v";
+ }
+
+ if (state.AudioStream != null)
+ {
+ args += string.Format(" -map 0:{0}", state.AudioStream.Index);
+ }
+
+ else
+ {
+ args += " -map -0:a";
+ }
+
+ if (state.SubtitleStream == null)
+ {
+ args += " -map -0:s";
+ }
+ else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
+ {
+ args += " -map 1:0 -sn";
+ }
+
+ return args;
+ }
+
+ /// <summary>
+ /// Determines whether the specified stream is H264.
+ /// </summary>
+ /// <param name="stream">The stream.</param>
+ /// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns>
+ protected bool IsH264(MediaStream stream)
+ {
+ var codec = stream.Codec ?? string.Empty;
+
+ return codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 ||
+ codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1;
+ }
+
+ /// <summary>
+ /// If we're going to put a fixed size on the command line, this will calculate it
+ /// </summary>
+ /// <param name="state">The state.</param>
+ /// <param name="outputVideoCodec">The output video codec.</param>
+ /// <param name="allowTimeStampCopy">if set to <c>true</c> [allow time stamp copy].</param>
+ /// <returns>System.String.</returns>
+ protected string GetOutputSizeParam(EncodingJob state,
+ string outputVideoCodec,
+ bool allowTimeStampCopy = true)
+ {
+ // http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
+
+ var request = state.Options;
+
+ var filters = new List<string>();
+
+ if (state.DeInterlace)
+ {
+ filters.Add("yadif=0:-1:0");
+ }
+
+ // If fixed dimensions were supplied
+ if (request.Width.HasValue && request.Height.HasValue)
+ {
+ var widthParam = request.Width.Value.ToString(UsCulture);
+ var heightParam = request.Height.Value.ToString(UsCulture);
+
+ filters.Add(string.Format("scale=trunc({0}/2)*2:trunc({1}/2)*2", widthParam, heightParam));
+ }
+
+ // If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size
+ else if (request.MaxWidth.HasValue && request.MaxHeight.HasValue)
+ {
+ var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
+ var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
+
+ filters.Add(string.Format("scale=trunc(min(iw\\,{0})/2)*2:trunc(min((iw/dar)\\,{1})/2)*2", maxWidthParam, maxHeightParam));
+ }
+
+ // If a fixed width was requested
+ else if (request.Width.HasValue)
+ {
+ var widthParam = request.Width.Value.ToString(UsCulture);
+
+ filters.Add(string.Format("scale={0}:trunc(ow/a/2)*2", widthParam));
+ }
+
+ // If a fixed height was requested
+ else if (request.Height.HasValue)
+ {
+ var heightParam = request.Height.Value.ToString(UsCulture);
+
+ filters.Add(string.Format("scale=trunc(oh*a*2)/2:{0}", heightParam));
+ }
+
+ // If a max width was requested
+ else if (request.MaxWidth.HasValue && (!request.MaxHeight.HasValue || state.VideoStream == null))
+ {
+ var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
+
+ filters.Add(string.Format("scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam));
+ }
+
+ // If a max height was requested
+ else if (request.MaxHeight.HasValue && (!request.MaxWidth.HasValue || state.VideoStream == null))
+ {
+ var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
+
+ filters.Add(string.Format("scale=trunc(oh*a*2)/2:min(ih\\,{0})", maxHeightParam));
+ }
+
+ else if (request.MaxWidth.HasValue ||
+ request.MaxHeight.HasValue ||
+ request.Width.HasValue ||
+ request.Height.HasValue)
+ {
+ if (state.VideoStream != null)
+ {
+ // Need to perform calculations manually
+
+ // Try to account for bad media info
+ var currentHeight = state.VideoStream.Height ?? request.MaxHeight ?? request.Height ?? 0;
+ var currentWidth = state.VideoStream.Width ?? request.MaxWidth ?? request.Width ?? 0;
+
+ var outputSize = DrawingUtils.Resize(currentWidth, currentHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight);
+
+ var manualWidthParam = outputSize.Width.ToString(UsCulture);
+ var manualHeightParam = outputSize.Height.ToString(UsCulture);
+
+ filters.Add(string.Format("scale=trunc({0}/2)*2:trunc({1}/2)*2", manualWidthParam, manualHeightParam));
+ }
+ }
+
+ var output = string.Empty;
+
+ if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream)
+ {
+ var subParam = GetTextSubtitleParam(state);
+
+ filters.Add(subParam);
+
+ if (allowTimeStampCopy)
+ {
+ output += " -copyts";
+ }
+ }
+
+ if (filters.Count > 0)
+ {
+ output += string.Format(" -vf \"{0}\"", string.Join(",", filters.ToArray()));
+ }
+
+ return output;
+ }
+
+ /// <summary>
+ /// Gets the text subtitle param.
+ /// </summary>
+ /// <param name="state">The state.</param>
+ /// <returns>System.String.</returns>
+ protected string GetTextSubtitleParam(EncodingJob state)
+ {
+ var seconds = Math.Round(TimeSpan.FromTicks(state.Options.StartTimeTicks ?? 0).TotalSeconds);
+
+ 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);
+
+ if (!string.IsNullOrEmpty(charenc))
+ {
+ charsetParam = ":charenc=" + charenc;
+ }
+ }
+
+ // TODO: Perhaps also use original_size=1920x800 ??
+ return string.Format("subtitles=filename='{0}'{1},setpts=PTS -{2}/TB",
+ subtitlePath.Replace('\\', '/').Replace(":/", "\\:/"),
+ charsetParam,
+ seconds.ToString(UsCulture));
+ }
+
+ return string.Format("subtitles='{0}:si={1}',setpts=PTS -{2}/TB",
+ state.MediaPath.Replace('\\', '/').Replace(":/", "\\:/"),
+ state.InternalSubtitleStreamOffset.ToString(UsCulture),
+ seconds.ToString(UsCulture));
+ }
+
+ protected string GetAudioFilterParam(EncodingJob state, bool isHls)
+ {
+ var volParam = string.Empty;
+ var audioSampleRate = string.Empty;
+
+ var channels = state.OutputAudioChannels;
+
+ // Boost volume to 200% when downsampling from 6ch to 2ch
+ if (channels.HasValue && channels.Value <= 2)
+ {
+ if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5)
+ {
+ volParam = ",volume=" + GetEncodingOptions().DownMixAudioBoost.ToString(UsCulture);
+ }
+ }
+
+ if (state.OutputAudioSampleRate.HasValue)
+ {
+ audioSampleRate = state.OutputAudioSampleRate.Value + ":";
+ }
+
+ var adelay = isHls ? "adelay=1," : string.Empty;
+
+ var pts = string.Empty;
+
+ if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream)
+ {
+ var seconds = TimeSpan.FromTicks(state.Options.StartTimeTicks ?? 0).TotalSeconds;
+
+ pts = string.Format(",asetpts=PTS-{0}/TB", Math.Round(seconds).ToString(UsCulture));
+ }
+
+ return string.Format("-af \"{0}aresample={1}async={4}{2}{3}\"",
+
+ adelay,
+ audioSampleRate,
+ volParam,
+ pts,
+ state.OutputAudioSync);
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs
new file mode 100644
index 000000000..40ca08c40
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs
@@ -0,0 +1,434 @@
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Drawing;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Net;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.MediaEncoding.Encoder
+{
+ public class EncodingJob : IDisposable
+ {
+ public bool HasExited { get; internal set; }
+
+ public Stream LogFileStream { get; set; }
+ public IProgress<double> Progress { get; set; }
+ public TaskCompletionSource<bool> TaskCompletionSource;
+
+ public EncodingJobOptions Options { get; set; }
+ public string InputContainer { get; set; }
+ public List<MediaStream> AllMediaStreams { get; set; }
+ public MediaStream AudioStream { get; set; }
+ public MediaStream VideoStream { get; set; }
+ public MediaStream SubtitleStream { get; set; }
+ public IIsoMount IsoMount { get; set; }
+
+ public bool ReadInputAtNativeFramerate { get; set; }
+ public bool IsVideoRequest { get; set; }
+ public string InputAudioSync { get; set; }
+ public string InputVideoSync { get; set; }
+ public string Id { get; set; }
+
+ public string MediaPath { get; set; }
+ public MediaProtocol InputProtocol { get; set; }
+ public bool IsInputVideo { get; set; }
+ public VideoType VideoType { get; set; }
+ public IsoType? IsoType { get; set; }
+ public List<string> PlayableStreamFileNames { get; set; }
+
+ public List<string> SupportedAudioCodecs { get; set; }
+ public Dictionary<string, string> RemoteHttpHeaders { get; set; }
+ public TransportStreamTimestamp InputTimestamp { get; set; }
+
+ public bool DeInterlace { get; set; }
+ public string MimeType { get; set; }
+ public bool EstimateContentLength { get; set; }
+ public bool EnableMpegtsM2TsMode { get; set; }
+ public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
+ public long? EncodingDurationTicks { get; set; }
+ public string LiveTvStreamId { get; set; }
+ public long? RunTimeTicks;
+
+ public string ItemType { get; set; }
+
+ public long? InputBitrate { get; set; }
+ public long? InputFileSize { get; set; }
+ public string OutputAudioSync = "1";
+ public string OutputVideoSync = "vfr";
+
+ public string GetMimeType(string outputPath)
+ {
+ if (!string.IsNullOrEmpty(MimeType))
+ {
+ return MimeType;
+ }
+
+ return MimeTypes.GetMimeType(outputPath);
+ }
+
+ private readonly ILogger _logger;
+ private readonly ILiveTvManager _liveTvManager;
+
+ public EncodingJob(ILogger logger, ILiveTvManager liveTvManager)
+ {
+ _logger = logger;
+ _liveTvManager = liveTvManager;
+ Id = Guid.NewGuid().ToString("N");
+
+ RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ _logger = logger;
+ SupportedAudioCodecs = new List<string>();
+ PlayableStreamFileNames = new List<string>();
+ RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ AllMediaStreams = new List<MediaStream>();
+ TaskCompletionSource = new TaskCompletionSource<bool>();
+ }
+
+ public void Dispose()
+ {
+ DisposeLiveStream();
+ DisposeLogStream();
+ DisposeIsoMount();
+ }
+
+ private void DisposeLogStream()
+ {
+ if (LogFileStream != null)
+ {
+ try
+ {
+ LogFileStream.Dispose();
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error disposing log stream", ex);
+ }
+
+ LogFileStream = null;
+ }
+ }
+
+ private void DisposeIsoMount()
+ {
+ if (IsoMount != null)
+ {
+ try
+ {
+ IsoMount.Dispose();
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error disposing iso mount", ex);
+ }
+
+ IsoMount = null;
+ }
+ }
+
+ private async void DisposeLiveStream()
+ {
+ if (!string.IsNullOrEmpty(LiveTvStreamId))
+ {
+ try
+ {
+ await _liveTvManager.CloseLiveStream(LiveTvStreamId, CancellationToken.None).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error closing live tv stream", ex);
+ }
+ }
+ }
+
+ public int InternalSubtitleStreamOffset { get; set; }
+
+ public string OutputFilePath { get; set; }
+ public string OutputVideoCodec { get; set; }
+ public string OutputAudioCodec { get; set; }
+ public int? OutputAudioChannels;
+ public int? OutputAudioSampleRate;
+ public int? OutputAudioBitrate;
+ public int? OutputVideoBitrate;
+
+ public string ActualOutputVideoCodec
+ {
+ get
+ {
+ var codec = OutputVideoCodec;
+
+ if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
+ {
+ var stream = VideoStream;
+
+ if (stream != null)
+ {
+ return stream.Codec;
+ }
+
+ return null;
+ }
+
+ return codec;
+ }
+ }
+
+ public string ActualOutputAudioCodec
+ {
+ get
+ {
+ var codec = OutputAudioCodec;
+
+ if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
+ {
+ var stream = AudioStream;
+
+ if (stream != null)
+ {
+ return stream.Codec;
+ }
+
+ return null;
+ }
+
+ return codec;
+ }
+ }
+
+ public int? TotalOutputBitrate
+ {
+ get
+ {
+ return (OutputAudioBitrate ?? 0) + (OutputVideoBitrate ?? 0);
+ }
+ }
+
+ public int? OutputWidth
+ {
+ get
+ {
+ if (VideoStream != null && VideoStream.Width.HasValue && VideoStream.Height.HasValue)
+ {
+ var size = new ImageSize
+ {
+ Width = VideoStream.Width.Value,
+ Height = VideoStream.Height.Value
+ };
+
+ var newSize = DrawingUtils.Resize(size,
+ Options.Width,
+ Options.Height,
+ Options.MaxWidth,
+ Options.MaxHeight);
+
+ return Convert.ToInt32(newSize.Width);
+ }
+
+ if (!IsVideoRequest)
+ {
+ return null;
+ }
+
+ return Options.MaxWidth ?? Options.Width;
+ }
+ }
+
+ public int? OutputHeight
+ {
+ get
+ {
+ if (VideoStream != null && VideoStream.Width.HasValue && VideoStream.Height.HasValue)
+ {
+ var size = new ImageSize
+ {
+ Width = VideoStream.Width.Value,
+ Height = VideoStream.Height.Value
+ };
+
+ var newSize = DrawingUtils.Resize(size,
+ Options.Width,
+ Options.Height,
+ Options.MaxWidth,
+ Options.MaxHeight);
+
+ return Convert.ToInt32(newSize.Height);
+ }
+
+ if (!IsVideoRequest)
+ {
+ return null;
+ }
+
+ return Options.MaxHeight ?? Options.Height;
+ }
+ }
+
+ /// <summary>
+ /// Predicts the audio sample rate that will be in the output stream
+ /// </summary>
+ public int? TargetVideoBitDepth
+ {
+ get
+ {
+ var stream = VideoStream;
+ return stream == null || !Options.Static ? null : stream.BitDepth;
+ }
+ }
+
+ /// <summary>
+ /// Gets the target reference frames.
+ /// </summary>
+ /// <value>The target reference frames.</value>
+ public int? TargetRefFrames
+ {
+ get
+ {
+ var stream = VideoStream;
+ return stream == null || !Options.Static ? null : stream.RefFrames;
+ }
+ }
+
+ /// <summary>
+ /// Predicts the audio sample rate that will be in the output stream
+ /// </summary>
+ public float? TargetFramerate
+ {
+ get
+ {
+ var stream = VideoStream;
+ var requestedFramerate = Options.MaxFramerate ?? Options.Framerate;
+
+ return requestedFramerate.HasValue && !Options.Static
+ ? requestedFramerate
+ : stream == null ? null : stream.AverageFrameRate ?? stream.RealFrameRate;
+ }
+ }
+
+ /// <summary>
+ /// Predicts the audio sample rate that will be in the output stream
+ /// </summary>
+ public double? TargetVideoLevel
+ {
+ get
+ {
+ var stream = VideoStream;
+ return Options.Level.HasValue && !Options.Static
+ ? Options.Level.Value
+ : stream == null ? null : stream.Level;
+ }
+ }
+
+ public TransportStreamTimestamp TargetTimestamp
+ {
+ get
+ {
+ var defaultValue = string.Equals(Options.OutputContainer, "m2ts", StringComparison.OrdinalIgnoreCase) ?
+ TransportStreamTimestamp.Valid :
+ TransportStreamTimestamp.None;
+
+ return !Options.Static
+ ? defaultValue
+ : InputTimestamp;
+ }
+ }
+
+ /// <summary>
+ /// Predicts the audio sample rate that will be in the output stream
+ /// </summary>
+ public int? TargetPacketLength
+ {
+ get
+ {
+ var stream = VideoStream;
+ return !Options.Static
+ ? null
+ : stream == null ? null : stream.PacketLength;
+ }
+ }
+
+ /// <summary>
+ /// Predicts the audio sample rate that will be in the output stream
+ /// </summary>
+ public string TargetVideoProfile
+ {
+ get
+ {
+ var stream = VideoStream;
+ return !string.IsNullOrEmpty(Options.Profile) && !Options.Static
+ ? Options.Profile
+ : stream == null ? null : stream.Profile;
+ }
+ }
+
+ public bool? IsTargetAnamorphic
+ {
+ get
+ {
+ if (Options.Static)
+ {
+ return VideoStream == null ? null : VideoStream.IsAnamorphic;
+ }
+
+ return false;
+ }
+ }
+
+ public bool? IsTargetCabac
+ {
+ get
+ {
+ if (Options.Static)
+ {
+ return VideoStream == null ? null : VideoStream.IsCabac;
+ }
+
+ return true;
+ }
+ }
+
+ public void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded)
+ {
+ var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null;
+
+ // job.Framerate = framerate;
+
+ if (percentComplete.HasValue)
+ {
+ Progress.Report(percentComplete.Value);
+ }
+
+ // job.TranscodingPositionTicks = ticks;
+ // job.BytesTranscoded = bytesTranscoded;
+
+ var deviceId = Options.DeviceId;
+
+ if (!string.IsNullOrWhiteSpace(deviceId))
+ {
+ var audioCodec = ActualOutputVideoCodec;
+ var videoCodec = ActualOutputVideoCodec;
+
+ // SessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo
+ // {
+ // Bitrate = job.TotalOutputBitrate,
+ // AudioCodec = audioCodec,
+ // VideoCodec = videoCodec,
+ // Container = job.Options.OutputContainer,
+ // Framerate = framerate,
+ // CompletionPercentage = percentComplete,
+ // Width = job.OutputWidth,
+ // Height = job.OutputHeight,
+ // AudioChannels = job.OutputAudioChannels,
+ // IsAudioDirect = string.Equals(job.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase),
+ // IsVideoDirect = string.Equals(job.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)
+ // });
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
new file mode 100644
index 000000000..00c7b61e3
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
@@ -0,0 +1,830 @@
+using MediaBrowser.Controller.Channels;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.MediaInfo;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.MediaEncoding.Encoder
+{
+ public class EncodingJobFactory
+ {
+ private readonly ILogger _logger;
+ private readonly ILiveTvManager _liveTvManager;
+ private readonly ILibraryManager _libraryManager;
+ private readonly IChannelManager _channelManager;
+
+ protected static readonly CultureInfo UsCulture = new CultureInfo("en-US");
+
+ public EncodingJobFactory(ILogger logger, ILiveTvManager liveTvManager, ILibraryManager libraryManager, IChannelManager channelManager)
+ {
+ _logger = logger;
+ _liveTvManager = liveTvManager;
+ _libraryManager = libraryManager;
+ _channelManager = channelManager;
+ }
+
+ public async Task<EncodingJob> CreateJob(EncodingJobOptions options, bool isVideoRequest, IProgress<double> progress, CancellationToken cancellationToken)
+ {
+ var request = options;
+
+ if (string.IsNullOrEmpty(request.AudioCodec))
+ {
+ request.AudioCodec = InferAudioCodec(request.OutputContainer);
+ }
+
+ var state = new EncodingJob(_logger, _liveTvManager)
+ {
+ Options = options,
+ IsVideoRequest = isVideoRequest,
+ Progress = progress
+ };
+
+ if (!string.IsNullOrWhiteSpace(request.AudioCodec))
+ {
+ state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+ request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault();
+ }
+
+ var item = _libraryManager.GetItemById(request.ItemId);
+
+ List<MediaStream> mediaStreams = null;
+
+ state.ItemType = item.GetType().Name;
+
+ if (item is ILiveTvRecording)
+ {
+ var recording = await _liveTvManager.GetInternalRecording(request.ItemId, cancellationToken).ConfigureAwait(false);
+
+ state.VideoType = VideoType.VideoFile;
+ state.IsInputVideo = string.Equals(recording.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
+
+ var path = recording.RecordingInfo.Path;
+ var mediaUrl = recording.RecordingInfo.Url;
+
+ var source = string.IsNullOrEmpty(request.MediaSourceId)
+ ? recording.GetMediaSources(false).First()
+ : recording.GetMediaSources(false).First(i => string.Equals(i.Id, request.MediaSourceId));
+
+ mediaStreams = source.MediaStreams;
+
+ // Just to prevent this from being null and causing other methods to fail
+ state.MediaPath = string.Empty;
+
+ if (!string.IsNullOrEmpty(path))
+ {
+ state.MediaPath = path;
+ state.InputProtocol = MediaProtocol.File;
+ }
+ else if (!string.IsNullOrEmpty(mediaUrl))
+ {
+ state.MediaPath = mediaUrl;
+ state.InputProtocol = MediaProtocol.Http;
+ }
+
+ state.RunTimeTicks = recording.RunTimeTicks;
+ state.DeInterlace = true;
+ state.OutputAudioSync = "1000";
+ state.InputVideoSync = "-1";
+ state.InputAudioSync = "1";
+ state.InputContainer = recording.Container;
+ state.ReadInputAtNativeFramerate = source.ReadAtNativeFramerate;
+ }
+ else if (item is LiveTvChannel)
+ {
+ var channel = _liveTvManager.GetInternalChannel(request.ItemId);
+
+ state.VideoType = VideoType.VideoFile;
+ state.IsInputVideo = string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
+ mediaStreams = new List<MediaStream>();
+
+ state.DeInterlace = true;
+
+ // Just to prevent this from being null and causing other methods to fail
+ state.MediaPath = string.Empty;
+ }
+ else if (item is IChannelMediaItem)
+ {
+ var mediaSource = await GetChannelMediaInfo(request.ItemId, request.MediaSourceId, cancellationToken).ConfigureAwait(false);
+ state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
+ state.InputProtocol = mediaSource.Protocol;
+ state.MediaPath = mediaSource.Path;
+ state.RunTimeTicks = item.RunTimeTicks;
+ state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
+ state.InputBitrate = mediaSource.Bitrate;
+ state.InputFileSize = mediaSource.Size;
+ state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
+ mediaStreams = mediaSource.MediaStreams;
+ }
+ else
+ {
+ var hasMediaSources = (IHasMediaSources)item;
+ var mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
+ ? hasMediaSources.GetMediaSources(false).First()
+ : hasMediaSources.GetMediaSources(false).First(i => string.Equals(i.Id, request.MediaSourceId));
+
+ mediaStreams = mediaSource.MediaStreams;
+
+ state.MediaPath = mediaSource.Path;
+ state.InputProtocol = mediaSource.Protocol;
+ state.InputContainer = mediaSource.Container;
+ state.InputFileSize = mediaSource.Size;
+ state.InputBitrate = mediaSource.Bitrate;
+ state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
+
+ var video = item as Video;
+
+ if (video != null)
+ {
+ state.IsInputVideo = true;
+
+ if (mediaSource.VideoType.HasValue)
+ {
+ state.VideoType = mediaSource.VideoType.Value;
+ }
+
+ state.IsoType = mediaSource.IsoType;
+
+ state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
+
+ if (mediaSource.Timestamp.HasValue)
+ {
+ state.InputTimestamp = mediaSource.Timestamp.Value;
+ }
+ }
+
+ state.RunTimeTicks = mediaSource.RunTimeTicks;
+ }
+
+ AttachMediaStreamInfo(state, mediaStreams, request);
+
+ state.OutputAudioBitrate = GetAudioBitrateParam(request, state.AudioStream);
+ state.OutputAudioSampleRate = request.AudioSampleRate;
+
+ state.OutputAudioCodec = GetAudioCodec(request);
+
+ state.OutputAudioChannels = GetNumAudioChannelsParam(request, state.AudioStream, state.OutputAudioCodec);
+
+ if (isVideoRequest)
+ {
+ state.OutputVideoCodec = GetVideoCodec(request);
+ state.OutputVideoBitrate = GetVideoBitrateParamValue(request, state.VideoStream);
+
+ if (state.OutputVideoBitrate.HasValue)
+ {
+ var resolution = ResolutionNormalizer.Normalize(state.OutputVideoBitrate.Value,
+ state.OutputVideoCodec,
+ request.MaxWidth,
+ request.MaxHeight);
+
+ request.MaxWidth = resolution.MaxWidth;
+ request.MaxHeight = resolution.MaxHeight;
+ }
+ }
+
+ ApplyDeviceProfileSettings(state);
+
+ if (isVideoRequest)
+ {
+ if (state.VideoStream != null && CanStreamCopyVideo(request, state.VideoStream))
+ {
+ state.OutputVideoCodec = "copy";
+ }
+
+ if (state.AudioStream != null && CanStreamCopyAudio(request, state.AudioStream, state.SupportedAudioCodecs))
+ {
+ state.OutputAudioCodec = "copy";
+ }
+ }
+
+ return state;
+ }
+
+ internal static void AttachMediaStreamInfo(EncodingJob state,
+ List<MediaStream> mediaStreams,
+ EncodingJobOptions videoRequest)
+ {
+ if (videoRequest != null)
+ {
+ if (string.IsNullOrEmpty(videoRequest.VideoCodec))
+ {
+ videoRequest.VideoCodec = InferVideoCodec(videoRequest.OutputContainer);
+ }
+
+ state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
+ state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Subtitle, false);
+ state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
+
+ if (state.SubtitleStream != null && !state.SubtitleStream.IsExternal)
+ {
+ state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal).ToList().IndexOf(state.SubtitleStream);
+ }
+
+ if (state.VideoStream != null && state.VideoStream.IsInterlaced)
+ {
+ state.DeInterlace = true;
+ }
+
+ EnforceResolutionLimit(state, videoRequest);
+ }
+ else
+ {
+ state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
+ }
+
+ state.AllMediaStreams = mediaStreams;
+ }
+
+ /// <summary>
+ /// Infers the video codec.
+ /// </summary>
+ /// <param name="container">The container.</param>
+ /// <returns>System.Nullable{VideoCodecs}.</returns>
+ private static string InferVideoCodec(string container)
+ {
+ if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase))
+ {
+ return "wmv";
+ }
+ if (string.Equals(container, "webm", StringComparison.OrdinalIgnoreCase))
+ {
+ return "vpx";
+ }
+ if (string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ogv", StringComparison.OrdinalIgnoreCase))
+ {
+ return "theora";
+ }
+ if (string.Equals(container, "m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase))
+ {
+ return "h264";
+ }
+
+ return "copy";
+ }
+
+ private string InferAudioCodec(string container)
+ {
+ if (string.Equals(container, "mp3", StringComparison.OrdinalIgnoreCase))
+ {
+ return "mp3";
+ }
+ if (string.Equals(container, "aac", StringComparison.OrdinalIgnoreCase))
+ {
+ return "aac";
+ }
+ if (string.Equals(container, "wma", StringComparison.OrdinalIgnoreCase))
+ {
+ return "wma";
+ }
+ if (string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase))
+ {
+ return "vorbis";
+ }
+ if (string.Equals(container, "oga", StringComparison.OrdinalIgnoreCase))
+ {
+ return "vorbis";
+ }
+ if (string.Equals(container, "ogv", StringComparison.OrdinalIgnoreCase))
+ {
+ return "vorbis";
+ }
+ if (string.Equals(container, "webm", StringComparison.OrdinalIgnoreCase))
+ {
+ return "vorbis";
+ }
+ if (string.Equals(container, "webma", StringComparison.OrdinalIgnoreCase))
+ {
+ return "vorbis";
+ }
+
+ return "copy";
+ }
+
+ /// <summary>
+ /// Determines which stream will be used for playback
+ /// </summary>
+ /// <param name="allStream">All stream.</param>
+ /// <param name="desiredIndex">Index of the desired.</param>
+ /// <param name="type">The type.</param>
+ /// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
+ /// <returns>MediaStream.</returns>
+ private static MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, bool returnFirstIfNoIndex = true)
+ {
+ var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
+
+ if (desiredIndex.HasValue)
+ {
+ var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
+
+ if (stream != null)
+ {
+ return stream;
+ }
+ }
+
+ if (type == MediaStreamType.Video)
+ {
+ streams = streams.Where(i => !string.Equals(i.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)).ToList();
+ }
+
+ if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
+ {
+ return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
+ streams.FirstOrDefault();
+ }
+
+ // Just return the first one
+ return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
+ }
+
+ /// <summary>
+ /// Enforces the resolution limit.
+ /// </summary>
+ /// <param name="state">The state.</param>
+ /// <param name="videoRequest">The video request.</param>
+ private static void EnforceResolutionLimit(EncodingJob state, EncodingJobOptions videoRequest)
+ {
+ // Switch the incoming params to be ceilings rather than fixed values
+ videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width;
+ videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height;
+
+ videoRequest.Width = null;
+ videoRequest.Height = null;
+ }
+
+ /// <summary>
+ /// Gets the number of audio channels to specify on the command line
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <param name="audioStream">The audio stream.</param>
+ /// <param name="outputAudioCodec">The output audio codec.</param>
+ /// <returns>System.Nullable{System.Int32}.</returns>
+ private int? GetNumAudioChannelsParam(EncodingJobOptions request, MediaStream audioStream, string outputAudioCodec)
+ {
+ if (audioStream != null)
+ {
+ var codec = outputAudioCodec ?? string.Empty;
+
+ if (audioStream.Channels > 2 && codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ // wmav2 currently only supports two channel output
+ return 2;
+ }
+ }
+
+ if (request.MaxAudioChannels.HasValue)
+ {
+ if (audioStream != null && audioStream.Channels.HasValue)
+ {
+ return Math.Min(request.MaxAudioChannels.Value, audioStream.Channels.Value);
+ }
+
+ // If we don't have any media info then limit it to 5 to prevent encoding errors due to asking for too many channels
+ return Math.Min(request.MaxAudioChannels.Value, 5);
+ }
+
+ return request.AudioChannels;
+ }
+
+ private int? GetVideoBitrateParamValue(EncodingJobOptions request, MediaStream videoStream)
+ {
+ var bitrate = request.VideoBitRate;
+
+ if (videoStream != null)
+ {
+ var isUpscaling = request.Height.HasValue && videoStream.Height.HasValue &&
+ request.Height.Value > videoStream.Height.Value;
+
+ if (request.Width.HasValue && videoStream.Width.HasValue &&
+ request.Width.Value > videoStream.Width.Value)
+ {
+ isUpscaling = true;
+ }
+
+ // Don't allow bitrate increases unless upscaling
+ if (!isUpscaling)
+ {
+ if (bitrate.HasValue && videoStream.BitRate.HasValue)
+ {
+ bitrate = Math.Min(bitrate.Value, videoStream.BitRate.Value);
+ }
+ }
+ }
+
+ return bitrate;
+ }
+
+ private async Task<MediaSourceInfo> GetChannelMediaInfo(string id,
+ string mediaSourceId,
+ CancellationToken cancellationToken)
+ {
+ var channelMediaSources = await _channelManager.GetChannelItemMediaSources(id, true, cancellationToken)
+ .ConfigureAwait(false);
+
+ var list = channelMediaSources.ToList();
+
+ if (!string.IsNullOrWhiteSpace(mediaSourceId))
+ {
+ var source = list
+ .FirstOrDefault(i => string.Equals(mediaSourceId, i.Id));
+
+ if (source != null)
+ {
+ return source;
+ }
+ }
+
+ return list.First();
+ }
+
+ protected string GetVideoBitrateParam(EncodingJob state, string videoCodec, bool isHls)
+ {
+ var bitrate = state.OutputVideoBitrate;
+
+ if (bitrate.HasValue)
+ {
+ var hasFixedResolution = state.Options.HasFixedResolution;
+
+ if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
+ {
+ if (hasFixedResolution)
+ {
+ return string.Format(" -minrate:v ({0}*.90) -maxrate:v ({0}*1.10) -bufsize:v {0} -b:v {0}", bitrate.Value.ToString(UsCulture));
+ }
+
+ // With vpx when crf is used, b:v becomes a max rate
+ // https://trac.ffmpeg.org/wiki/vpxEncodingGuide. But higher bitrate source files -b:v causes judder so limite the bitrate but dont allow it to "saturate" the bitrate. So dont contrain it down just up.
+ return string.Format(" -maxrate:v {0} -bufsize:v ({0}*2) -b:v {0}", bitrate.Value.ToString(UsCulture));
+ }
+
+ if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
+ {
+ return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
+ }
+
+ // H264
+ if (hasFixedResolution)
+ {
+ if (isHls)
+ {
+ return string.Format(" -b:v {0} -maxrate ({0}*.80) -bufsize {0}", bitrate.Value.ToString(UsCulture));
+ }
+
+ return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
+ }
+
+ return string.Format(" -maxrate {0} -bufsize {1}",
+ bitrate.Value.ToString(UsCulture),
+ (bitrate.Value * 2).ToString(UsCulture));
+ }
+
+ return string.Empty;
+ }
+
+ private int? GetAudioBitrateParam(EncodingJobOptions request, MediaStream audioStream)
+ {
+ if (request.AudioBitRate.HasValue)
+ {
+ // Make sure we don't request a bitrate higher than the source
+ var currentBitrate = audioStream == null ? request.AudioBitRate.Value : audioStream.BitRate ?? request.AudioBitRate.Value;
+
+ return request.AudioBitRate.Value;
+ //return Math.Min(currentBitrate, request.AudioBitRate.Value);
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Determines whether the specified stream is H264.
+ /// </summary>
+ /// <param name="stream">The stream.</param>
+ /// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns>
+ protected bool IsH264(MediaStream stream)
+ {
+ var codec = stream.Codec ?? string.Empty;
+
+ return codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 ||
+ codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1;
+ }
+
+ /// <summary>
+ /// Gets the name of the output audio codec
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <returns>System.String.</returns>
+ private string GetAudioCodec(EncodingJobOptions request)
+ {
+ var codec = request.AudioCodec;
+
+ if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
+ {
+ return "aac -strict experimental";
+ }
+ if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
+ {
+ return "libmp3lame";
+ }
+ if (string.Equals(codec, "vorbis", StringComparison.OrdinalIgnoreCase))
+ {
+ return "libvorbis";
+ }
+ if (string.Equals(codec, "wma", StringComparison.OrdinalIgnoreCase))
+ {
+ return "wmav2";
+ }
+
+ return (codec ?? string.Empty).ToLower();
+ }
+
+ /// <summary>
+ /// Gets the name of the output video codec
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <returns>System.String.</returns>
+ private string GetVideoCodec(EncodingJobOptions request)
+ {
+ var codec = request.VideoCodec;
+
+ if (!string.IsNullOrEmpty(codec))
+ {
+ if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
+ {
+ return "libx264";
+ }
+ if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase))
+ {
+ return "libx265";
+ }
+ if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase))
+ {
+ return "libvpx";
+ }
+ if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase))
+ {
+ return "wmv2";
+ }
+ if (string.Equals(codec, "theora", StringComparison.OrdinalIgnoreCase))
+ {
+ return "libtheora";
+ }
+
+ return codec.ToLower();
+ }
+
+ return "copy";
+ }
+
+ internal static bool CanStreamCopyVideo(EncodingJobOptions request, MediaStream videoStream)
+ {
+ if (videoStream.IsInterlaced)
+ {
+ return false;
+ }
+
+ // Can't stream copy if we're burning in subtitles
+ if (request.SubtitleStreamIndex.HasValue)
+ {
+ if (request.SubtitleMethod == SubtitleDeliveryMethod.Encode)
+ {
+ return false;
+ }
+ }
+
+ // Source and target codecs must match
+ if (!string.Equals(request.VideoCodec, videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ // If client is requesting a specific video profile, it must match the source
+ if (!string.IsNullOrEmpty(request.Profile))
+ {
+ if (string.IsNullOrEmpty(videoStream.Profile))
+ {
+ return false;
+ }
+
+ if (!string.Equals(request.Profile, videoStream.Profile, StringComparison.OrdinalIgnoreCase))
+ {
+ var currentScore = GetVideoProfileScore(videoStream.Profile);
+ var requestedScore = GetVideoProfileScore(request.Profile);
+
+ if (currentScore == -1 || currentScore > requestedScore)
+ {
+ return false;
+ }
+ }
+ }
+
+ // Video width must fall within requested value
+ if (request.MaxWidth.HasValue)
+ {
+ if (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value)
+ {
+ return false;
+ }
+ }
+
+ // Video height must fall within requested value
+ if (request.MaxHeight.HasValue)
+ {
+ if (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value)
+ {
+ return false;
+ }
+ }
+
+ // Video framerate must fall within requested value
+ var requestedFramerate = request.MaxFramerate ?? request.Framerate;
+ if (requestedFramerate.HasValue)
+ {
+ var videoFrameRate = videoStream.AverageFrameRate ?? videoStream.RealFrameRate;
+
+ if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value)
+ {
+ return false;
+ }
+ }
+
+ // Video bitrate must fall within requested value
+ if (request.VideoBitRate.HasValue)
+ {
+ if (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value)
+ {
+ return false;
+ }
+ }
+
+ if (request.MaxVideoBitDepth.HasValue)
+ {
+ if (videoStream.BitDepth.HasValue && videoStream.BitDepth.Value > request.MaxVideoBitDepth.Value)
+ {
+ return false;
+ }
+ }
+
+ if (request.MaxRefFrames.HasValue)
+ {
+ if (videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > request.MaxRefFrames.Value)
+ {
+ return false;
+ }
+ }
+
+ // If a specific level was requested, the source must match or be less than
+ if (request.Level.HasValue)
+ {
+ if (!videoStream.Level.HasValue)
+ {
+ return false;
+ }
+
+ if (videoStream.Level.Value > request.Level.Value)
+ {
+ return false;
+ }
+ }
+
+ if (request.Cabac.HasValue && request.Cabac.Value)
+ {
+ if (videoStream.IsCabac.HasValue && !videoStream.IsCabac.Value)
+ {
+ return false;
+ }
+ }
+
+ return request.EnableAutoStreamCopy;
+ }
+
+ private static int GetVideoProfileScore(string profile)
+ {
+ var list = new List<string>
+ {
+ "Constrained Baseline",
+ "Baseline",
+ "Extended",
+ "Main",
+ "High",
+ "Progressive High",
+ "Constrained High"
+ };
+
+ return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase));
+ }
+
+ internal static bool CanStreamCopyAudio(EncodingJobOptions request, MediaStream audioStream, List<string> supportedAudioCodecs)
+ {
+ // Source and target codecs must match
+ if (string.IsNullOrEmpty(audioStream.Codec) || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ // Video bitrate must fall within requested value
+ if (request.AudioBitRate.HasValue)
+ {
+ if (!audioStream.BitRate.HasValue || audioStream.BitRate.Value <= 0)
+ {
+ return false;
+ }
+ if (audioStream.BitRate.Value > request.AudioBitRate.Value)
+ {
+ return false;
+ }
+ }
+
+ // Channels must fall within requested value
+ var channels = request.AudioChannels ?? request.MaxAudioChannels;
+ if (channels.HasValue)
+ {
+ if (!audioStream.Channels.HasValue || audioStream.Channels.Value <= 0)
+ {
+ return false;
+ }
+ if (audioStream.Channels.Value > channels.Value)
+ {
+ return false;
+ }
+ }
+
+ // Sample rate must fall within requested value
+ if (request.AudioSampleRate.HasValue)
+ {
+ if (!audioStream.SampleRate.HasValue || audioStream.SampleRate.Value <= 0)
+ {
+ return false;
+ }
+ if (audioStream.SampleRate.Value > request.AudioSampleRate.Value)
+ {
+ return false;
+ }
+ }
+
+ return request.EnableAutoStreamCopy;
+ }
+
+ private void ApplyDeviceProfileSettings(EncodingJob state)
+ {
+ var profile = state.Options.DeviceProfile;
+
+ if (profile == null)
+ {
+ // Don't use settings from the default profile.
+ // Only use a specific profile if it was requested.
+ return;
+ }
+
+ var audioCodec = state.ActualOutputAudioCodec;
+
+ var videoCodec = state.ActualOutputVideoCodec;
+ var outputContainer = state.Options.OutputContainer;
+
+ var mediaProfile = state.IsVideoRequest ?
+ profile.GetAudioMediaProfile(outputContainer, audioCodec, state.OutputAudioChannels, state.OutputAudioBitrate) :
+ profile.GetVideoMediaProfile(outputContainer,
+ audioCodec,
+ videoCodec,
+ state.OutputAudioBitrate,
+ state.OutputAudioChannels,
+ state.OutputWidth,
+ state.OutputHeight,
+ state.TargetVideoBitDepth,
+ state.OutputVideoBitrate,
+ state.TargetVideoProfile,
+ state.TargetVideoLevel,
+ state.TargetFramerate,
+ state.TargetPacketLength,
+ state.TargetTimestamp,
+ state.IsTargetAnamorphic,
+ state.IsTargetCabac,
+ state.TargetRefFrames);
+
+ if (mediaProfile != null)
+ {
+ state.MimeType = mediaProfile.MimeType;
+ }
+
+ var transcodingProfile = state.IsVideoRequest ?
+ profile.GetAudioTranscodingProfile(outputContainer, audioCodec) :
+ profile.GetVideoTranscodingProfile(outputContainer, audioCodec, videoCodec);
+
+ if (transcodingProfile != null)
+ {
+ state.EstimateContentLength = transcodingProfile.EstimateContentLength;
+ state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
+ state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
index c76f78009..b34a4cd38 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
@@ -41,7 +41,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
// If there's more than one we'll need to use the concat command
if (inputFiles.Count > 1)
{
- var files = string.Join("|", inputFiles);
+ var files = string.Join("|", inputFiles.Select(NormalizePath).ToArray());
return string.Format("concat:\"{0}\"", files);
}
@@ -57,9 +57,23 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <returns>System.String.</returns>
private static string GetFileInputArgument(string path)
{
+ // Quotes are valid path characters in linux and they need to be escaped here with a leading \
+ path = NormalizePath(path);
+
return string.Format("file:\"{0}\"", path);
}
+ /// <summary>
+ /// Normalizes the path.
+ /// </summary>
+ /// <param name="path">The path.</param>
+ /// <returns>System.String.</returns>
+ private static string NormalizePath(string path)
+ {
+ // Quotes are valid path characters in linux and they need to be escaped here with a leading \
+ return path.Replace("\"", "\\\"");
+ }
+
public static string GetProbeSizeArgument(bool isDvd)
{
return isDvd ? "-probesize 1G -analyzeduration 200M" : string.Empty;
diff --git a/MediaBrowser.MediaEncoding/Encoder/JobLogger.cs b/MediaBrowser.MediaEncoding/Encoder/JobLogger.cs
new file mode 100644
index 000000000..6be870519
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Encoder/JobLogger.cs
@@ -0,0 +1,122 @@
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Model.Logging;
+using System;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace MediaBrowser.MediaEncoding.Encoder
+{
+ public class JobLogger
+ {
+ private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+ private readonly ILogger _logger;
+
+ public JobLogger(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public async void StartStreamingLog(EncodingJob transcodingJob, Stream source, Stream target)
+ {
+ try
+ {
+ using (var reader = new StreamReader(source))
+ {
+ while (!reader.EndOfStream)
+ {
+ var line = await reader.ReadLineAsync().ConfigureAwait(false);
+
+ ParseLogLine(line, transcodingJob);
+
+ var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
+
+ await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error reading ffmpeg log", ex);
+ }
+ }
+
+ private void ParseLogLine(string line, EncodingJob transcodingJob)
+ {
+ float? framerate = null;
+ double? percent = null;
+ TimeSpan? transcodingPosition = null;
+ long? bytesTranscoded = null;
+
+ var parts = line.Split(' ');
+
+ var totalMs = transcodingJob.RunTimeTicks.HasValue
+ ? TimeSpan.FromTicks(transcodingJob.RunTimeTicks.Value).TotalMilliseconds
+ : 0;
+
+ var startMs = transcodingJob.Options.StartTimeTicks.HasValue
+ ? TimeSpan.FromTicks(transcodingJob.Options.StartTimeTicks.Value).TotalMilliseconds
+ : 0;
+
+ for (var i = 0; i < parts.Length; i++)
+ {
+ var part = parts[i];
+
+ if (string.Equals(part, "fps=", StringComparison.OrdinalIgnoreCase) &&
+ (i + 1 < parts.Length))
+ {
+ var rate = parts[i + 1];
+ float val;
+
+ if (float.TryParse(rate, NumberStyles.Any, _usCulture, out val))
+ {
+ framerate = val;
+ }
+ }
+ else if (transcodingJob.RunTimeTicks.HasValue &&
+ part.StartsWith("time=", StringComparison.OrdinalIgnoreCase))
+ {
+ var time = part.Split(new[] { '=' }, 2).Last();
+ TimeSpan val;
+
+ if (TimeSpan.TryParse(time, _usCulture, out val))
+ {
+ var currentMs = startMs + val.TotalMilliseconds;
+
+ var percentVal = currentMs / totalMs;
+ percent = 100 * percentVal;
+
+ transcodingPosition = val;
+ }
+ }
+ else if (part.StartsWith("size=", StringComparison.OrdinalIgnoreCase))
+ {
+ var size = part.Split(new[] { '=' }, 2).Last();
+
+ int? scale = null;
+ if (size.IndexOf("kb", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ scale = 1024;
+ size = size.Replace("kb", string.Empty, StringComparison.OrdinalIgnoreCase);
+ }
+
+ if (scale.HasValue)
+ {
+ long val;
+
+ if (long.TryParse(size, NumberStyles.Any, _usCulture, out val))
+ {
+ bytesTranscoded = val * scale.Value;
+ }
+ }
+ }
+ }
+
+ if (framerate.HasValue || percent.HasValue)
+ {
+ transcodingJob.ReportTranscodingProgress(transcodingPosition, framerate, percent, bytesTranscoded);
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 7fb27e25f..e800b4254 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -1,10 +1,16 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Channels;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
using System;
-using System.Collections.Concurrent;
using System.Diagnostics;
using System.Globalization;
using System.IO;
@@ -51,11 +57,28 @@ namespace MediaBrowser.MediaEncoding.Encoder
public string Version { get; private set; }
- public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, string version)
+ protected readonly IServerConfigurationManager ConfigurationManager;
+ protected readonly IFileSystem FileSystem;
+ protected readonly ILiveTvManager LiveTvManager;
+ protected readonly IIsoManager IsoManager;
+ protected readonly ILibraryManager LibraryManager;
+ protected readonly IChannelManager ChannelManager;
+ protected readonly ISessionManager SessionManager;
+ protected readonly Func<ISubtitleEncoder> SubtitleEncoder;
+
+ public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, string version, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func<ISubtitleEncoder> subtitleEncoder)
{
_logger = logger;
_jsonSerializer = jsonSerializer;
Version = version;
+ ConfigurationManager = configurationManager;
+ FileSystem = fileSystem;
+ LiveTvManager = liveTvManager;
+ IsoManager = isoManager;
+ LibraryManager = libraryManager;
+ ChannelManager = channelManager;
+ SessionManager = sessionManager;
+ SubtitleEncoder = subtitleEncoder;
FFProbePath = ffProbePath;
FFMpegPath = ffMpegPath;
}
@@ -473,7 +496,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
};
_logger.Info(process.StartInfo.FileName + " " + process.StartInfo.Arguments);
-
+
await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
process.Start();
@@ -511,5 +534,47 @@ namespace MediaBrowser.MediaEncoding.Encoder
throw new ApplicationException(msg);
}
}
+
+ public async Task<string> EncodeAudio(EncodingJobOptions options,
+ IProgress<double> progress,
+ CancellationToken cancellationToken)
+ {
+ var job = await new AudioEncoder(this,
+ _logger,
+ ConfigurationManager,
+ FileSystem,
+ LiveTvManager,
+ IsoManager,
+ LibraryManager,
+ ChannelManager,
+ SessionManager,
+ SubtitleEncoder())
+ .Start(options, progress, cancellationToken).ConfigureAwait(false);
+
+ await job.TaskCompletionSource.Task.ConfigureAwait(false);
+
+ return job.OutputFilePath;
+ }
+
+ public async Task<string> EncodeVideo(EncodingJobOptions options,
+ IProgress<double> progress,
+ CancellationToken cancellationToken)
+ {
+ var job = await new VideoEncoder(this,
+ _logger,
+ ConfigurationManager,
+ FileSystem,
+ LiveTvManager,
+ IsoManager,
+ LibraryManager,
+ ChannelManager,
+ SessionManager,
+ SubtitleEncoder())
+ .Start(options, progress, cancellationToken).ConfigureAwait(false);
+
+ await job.TaskCompletionSource.Task.ConfigureAwait(false);
+
+ return job.OutputFilePath;
+ }
}
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs
new file mode 100644
index 000000000..36406e3a3
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs
@@ -0,0 +1,177 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Channels;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using System;
+using System.IO;
+
+namespace MediaBrowser.MediaEncoding.Encoder
+{
+ public class VideoEncoder : BaseEncoder
+ {
+ public VideoEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder) : base(mediaEncoder, logger, configurationManager, fileSystem, liveTvManager, isoManager, libraryManager, channelManager, sessionManager, subtitleEncoder)
+ {
+ }
+
+ protected override string GetCommandLineArguments(EncodingJob state)
+ {
+ // Get the output codec name
+ var videoCodec = state.OutputVideoCodec;
+
+ var format = string.Empty;
+ var keyFrame = string.Empty;
+
+ if (string.Equals(Path.GetExtension(state.OutputFilePath), ".mp4", StringComparison.OrdinalIgnoreCase))
+ {
+ format = " -f mp4 -movflags frag_keyframe+empty_moov";
+ }
+
+ var threads = GetNumberOfThreads(state, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase));
+
+ var inputModifier = GetInputModifier(state);
+
+ return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"",
+ inputModifier,
+ GetInputArgument(state),
+ keyFrame,
+ GetMapArgs(state),
+ GetVideoArguments(state, videoCodec),
+ threads,
+ GetAudioArguments(state),
+ format,
+ state.OutputFilePath
+ ).Trim();
+ }
+
+ /// <summary>
+ /// Gets video arguments to pass to ffmpeg
+ /// </summary>
+ /// <param name="state">The state.</param>
+ /// <param name="codec">The video codec.</param>
+ /// <returns>System.String.</returns>
+ private string GetVideoArguments(EncodingJob state, string codec)
+ {
+ var args = "-codec:v:0 " + codec;
+
+ if (state.EnableMpegtsM2TsMode)
+ {
+ args += " -mpegts_m2ts_mode 1";
+ }
+
+ // See if we can save come cpu cycles by avoiding encoding
+ if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
+ {
+ return state.VideoStream != null && IsH264(state.VideoStream) && string.Equals(state.Options.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) ?
+ args + " -bsf:v h264_mp4toannexb" :
+ args;
+ }
+
+ var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
+ 5.ToString(UsCulture));
+
+ args += keyFrameArg;
+
+ var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
+
+ // Add resolution params, if specified
+ if (!hasGraphicalSubs)
+ {
+ args += GetOutputSizeParam(state, codec);
+ }
+
+ var qualityParam = GetVideoQualityParam(state, codec, false);
+
+ if (!string.IsNullOrEmpty(qualityParam))
+ {
+ args += " " + qualityParam.Trim();
+ }
+
+ // This is for internal graphical subs
+ if (hasGraphicalSubs)
+ {
+ args += GetGraphicalSubtitleParam(state, codec);
+ }
+
+ return args;
+ }
+
+ /// <summary>
+ /// Gets audio arguments to pass to ffmpeg
+ /// </summary>
+ /// <param name="state">The state.</param>
+ /// <returns>System.String.</returns>
+ private string GetAudioArguments(EncodingJob state)
+ {
+ // If the video doesn't have an audio stream, return a default.
+ if (state.AudioStream == null && state.VideoStream != null)
+ {
+ return string.Empty;
+ }
+
+ // Get the output codec name
+ var codec = state.OutputAudioCodec;
+
+ var args = "-codec:a:0 " + codec;
+
+ if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
+ {
+ return args;
+ }
+
+ // Add the number of audio channels
+ var channels = state.OutputAudioChannels;
+
+ if (channels.HasValue)
+ {
+ args += " -ac " + channels.Value;
+ }
+
+ var bitrate = state.OutputAudioBitrate;
+
+ if (bitrate.HasValue)
+ {
+ args += " -ab " + bitrate.Value.ToString(UsCulture);
+ }
+
+ args += " " + GetAudioFilterParam(state, false);
+
+ return args;
+ }
+
+ protected override string GetOutputFileExtension(EncodingJob state)
+ {
+ var ext = base.GetOutputFileExtension(state);
+
+ if (!string.IsNullOrEmpty(ext))
+ {
+ return ext;
+ }
+
+ var videoCodec = state.Options.VideoCodec;
+
+ if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
+ {
+ return ".ts";
+ }
+ if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase))
+ {
+ return ".ogv";
+ }
+ if (string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase))
+ {
+ return ".webm";
+ }
+ if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase))
+ {
+ return ".asf";
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index 6f59b7bec..38d8fa138 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -56,8 +56,15 @@
<Link>Properties\SharedVersion.cs</Link>
</Compile>
<Compile Include="BdInfo\BdInfoExaminer.cs" />
+ <Compile Include="Configuration\EncodingConfigurationFactory.cs" />
+ <Compile Include="Encoder\AudioEncoder.cs" />
+ <Compile Include="Encoder\BaseEncoder.cs" />
+ <Compile Include="Encoder\EncodingJob.cs" />
+ <Compile Include="Encoder\EncodingJobFactory.cs" />
<Compile Include="Encoder\EncodingUtils.cs" />
+ <Compile Include="Encoder\JobLogger.cs" />
<Compile Include="Encoder\MediaEncoder.cs" />
+ <Compile Include="Encoder\VideoEncoder.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Subtitles\ISubtitleParser.cs" />
<Compile Include="Subtitles\ISubtitleWriter.cs" />
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index 7c512840b..67c9123f5 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -613,6 +613,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
}
+ catch (DirectoryNotFoundException)
+ {
+
+ }
catch (IOException ex)
{
_logger.ErrorException("Error deleting extracted subtitle {0}", ex, outputPath);
diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
index 2629dac8b..c49e3e303 100644
--- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
+++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
@@ -10,8 +10,8 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MediaBrowser.Model</RootNamespace>
<AssemblyName>MediaBrowser.Model</AssemblyName>
- <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
- <TargetFrameworkProfile>Profile344</TargetFrameworkProfile>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <TargetFrameworkProfile>Profile259</TargetFrameworkProfile>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
@@ -188,6 +188,9 @@
<Compile Include="..\MediaBrowser.Model\Configuration\DynamicDayOfWeek.cs">
<Link>Configuration\DynamicDayOfWeek.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Configuration\EncodingOptions.cs">
+ <Link>Configuration\EncodingOptions.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\EncodingQuality.cs">
<Link>Configuration\EncodingQuality.cs</Link>
</Compile>
@@ -425,9 +428,6 @@
<Compile Include="..\MediaBrowser.Model\Dto\ChapterInfoDto.cs">
<Link>Dto\ChapterInfoDto.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Dto\DtoOptions.cs">
- <Link>Dto\DtoOptions.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Dto\GameSystemSummary.cs">
<Link>Dto\GameSystemSummary.cs</Link>
</Compile>
@@ -461,6 +461,12 @@
<Compile Include="..\MediaBrowser.Model\Dto\MediaSourceType.cs">
<Link>Dto\MediaSourceType.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Dto\MetadataEditorInfo.cs">
+ <Link>Dto\MetadataEditorInfo.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Model\Dto\NameValuePair.cs">
+ <Link>Dto\NameValuePair.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Dto\RatingType.cs">
<Link>Dto\RatingType.cs</Link>
</Compile>
@@ -470,9 +476,6 @@
<Compile Include="..\MediaBrowser.Model\Dto\RecommendationType.cs">
<Link>Dto\RecommendationType.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Dto\StreamOptions.cs">
- <Link>Dto\StreamOptions.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Dto\StudioDto.cs">
<Link>Dto\StudioDto.cs</Link>
</Compile>
@@ -788,6 +791,12 @@
<Compile Include="..\MediaBrowser.Model\Net\HttpException.cs">
<Link>Net\HttpException.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Net\HttpResponse.cs">
+ <Link>Net\HttpResponse.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Model\Net\MimeTypes.cs">
+ <Link>Net\MimeTypes.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Net\NetworkShare.cs">
<Link>Net\NetworkShare.cs</Link>
</Compile>
@@ -1028,12 +1037,36 @@
<Compile Include="..\MediaBrowser.Model\Session\UserDataChangeInfo.cs">
<Link>Session\UserDataChangeInfo.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Sync\DeviceFileInfo.cs">
+ <Link>Sync\DeviceFileInfo.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Model\Sync\ItemFIleInfo.cs">
+ <Link>Sync\ItemFIleInfo.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Model\Sync\ItemFileType.cs">
+ <Link>Sync\ItemFileType.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Model\Sync\LocalItem.cs">
+ <Link>Sync\LocalItem.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Sync\SyncCategory.cs">
<Link>Sync\SyncCategory.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Sync\SyncDataRequest.cs">
+ <Link>Sync\SyncDataRequest.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Model\Sync\SyncDataResponse.cs">
+ <Link>Sync\SyncDataResponse.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Sync\SyncDialogOptions.cs">
<Link>Sync\SyncDialogOptions.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Sync\SyncedItem.cs">
+ <Link>Sync\SyncedItem.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Model\Sync\SyncHelper.cs">
+ <Link>Sync\SyncHelper.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Sync\SyncJob.cs">
<Link>Sync\SyncJob.cs</Link>
</Compile>
@@ -1136,6 +1169,12 @@
<Compile Include="..\MediaBrowser.Model\Users\PinRedeemResult.cs">
<Link>Users\PinRedeemResult.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Users\UserAction.cs">
+ <Link>Users\UserAction.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Model\Users\UserActionType.cs">
+ <Link>Users\UserActionType.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Users\UserPolicy.cs">
<Link>Users\UserPolicy.cs</Link>
</Compile>
diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
index 4840777a5..d8a29e8da 100644
--- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
+++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
@@ -153,6 +153,9 @@
<Compile Include="..\MediaBrowser.Model\Configuration\DynamicDayOfWeek.cs">
<Link>Configuration\DynamicDayOfWeek.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Configuration\EncodingOptions.cs">
+ <Link>Configuration\EncodingOptions.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\EncodingQuality.cs">
<Link>Configuration\EncodingQuality.cs</Link>
</Compile>
@@ -390,9 +393,6 @@
<Compile Include="..\MediaBrowser.Model\Dto\ChapterInfoDto.cs">
<Link>Dto\ChapterInfoDto.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Dto\DtoOptions.cs">
- <Link>Dto\DtoOptions.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Dto\GameSystemSummary.cs">
<Link>Dto\GameSystemSummary.cs</Link>
</Compile>
@@ -426,6 +426,12 @@
<Compile Include="..\MediaBrowser.Model\Dto\MediaSourceType.cs">
<Link>Dto\MediaSourceType.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Dto\MetadataEditorInfo.cs">
+ <Link>Dto\MetadataEditorInfo.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Model\Dto\NameValuePair.cs">
+ <Link>Dto\NameValuePair.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Dto\RatingType.cs">
<Link>Dto\RatingType.cs</Link>
</Compile>
@@ -435,9 +441,6 @@
<Compile Include="..\MediaBrowser.Model\Dto\RecommendationType.cs">
<Link>Dto\RecommendationType.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Dto\StreamOptions.cs">
- <Link>Dto\StreamOptions.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Dto\StudioDto.cs">
<Link>Dto\StudioDto.cs</Link>
</Compile>
@@ -747,6 +750,12 @@
<Compile Include="..\MediaBrowser.Model\Net\HttpException.cs">
<Link>Net\HttpException.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Net\HttpResponse.cs">
+ <Link>Net\HttpResponse.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Model\Net\MimeTypes.cs">
+ <Link>Net\MimeTypes.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Net\NetworkShare.cs">
<Link>Net\NetworkShare.cs</Link>
</Compile>
@@ -987,12 +996,36 @@
<Compile Include="..\MediaBrowser.Model\Session\UserDataChangeInfo.cs">
<Link>Session\UserDataChangeInfo.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Sync\DeviceFileInfo.cs">
+ <Link>Sync\DeviceFileInfo.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Model\Sync\ItemFIleInfo.cs">
+ <Link>Sync\ItemFIleInfo.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Model\Sync\ItemFileType.cs">
+ <Link>Sync\ItemFileType.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Model\Sync\LocalItem.cs">
+ <Link>Sync\LocalItem.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Sync\SyncCategory.cs">
<Link>Sync\SyncCategory.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Sync\SyncDataRequest.cs">
+ <Link>Sync\SyncDataRequest.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Model\Sync\SyncDataResponse.cs">
+ <Link>Sync\SyncDataResponse.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Sync\SyncDialogOptions.cs">
<Link>Sync\SyncDialogOptions.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Sync\SyncedItem.cs">
+ <Link>Sync\SyncedItem.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Model\Sync\SyncHelper.cs">
+ <Link>Sync\SyncHelper.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Sync\SyncJob.cs">
<Link>Sync\SyncJob.cs</Link>
</Compile>
@@ -1095,6 +1128,12 @@
<Compile Include="..\MediaBrowser.Model\Users\PinRedeemResult.cs">
<Link>Users\PinRedeemResult.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Users\UserAction.cs">
+ <Link>Users\UserAction.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Model\Users\UserActionType.cs">
+ <Link>Users\UserActionType.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Users\UserPolicy.cs">
<Link>Users\UserPolicy.cs</Link>
</Compile>
diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs
index 9521f8538..9faa8fced 100644
--- a/MediaBrowser.Model/ApiClient/IApiClient.cs
+++ b/MediaBrowser.Model/ApiClient/IApiClient.cs
@@ -7,6 +7,7 @@ using MediaBrowser.Model.Events;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Net;
using MediaBrowser.Model.Notifications;
using MediaBrowser.Model.Playlists;
using MediaBrowser.Model.Plugins;
@@ -186,6 +187,30 @@ namespace MediaBrowser.Model.ApiClient
Task<Stream> GetImageStreamAsync(string url, CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
+ /// Gets the stream.
+ /// </summary>
+ /// <param name="url">The URL.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task&lt;Stream&gt;.</returns>
+ Task<Stream> GetStream(string url, CancellationToken cancellationToken = default(CancellationToken));
+
+ /// <summary>
+ /// Gets the response.
+ /// </summary>
+ /// <param name="url">The URL.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task&lt;HttpResponse&gt;.</returns>
+ Task<HttpResponse> GetResponse(string url, CancellationToken cancellationToken = default(CancellationToken));
+
+ /// <summary>
+ /// Updates the user configuration.
+ /// </summary>
+ /// <param name="userId">The user identifier.</param>
+ /// <param name="configuration">The configuration.</param>
+ /// <returns>Task.</returns>
+ Task UpdateUserConfiguration(string userId, UserConfiguration configuration);
+
+ /// <summary>
/// Gets a BaseItem
/// </summary>
/// <param name="id">The id.</param>
@@ -497,15 +522,6 @@ namespace MediaBrowser.Model.ApiClient
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;PublicSystemInfo&gt;.</returns>
Task<PublicSystemInfo> GetPublicSystemInfoAsync(CancellationToken cancellationToken = default(CancellationToken));
-
- /// <summary>
- /// Gets a person
- /// </summary>
- /// <param name="name">The name.</param>
- /// <param name="userId">The user id.</param>
- /// <returns>Task{BaseItemDto}.</returns>
- /// <exception cref="ArgumentNullException">userId</exception>
- Task<BaseItemDto> GetPersonAsync(string name, string userId);
/// <summary>
/// Gets a list of plugins installed on the server
@@ -962,15 +978,6 @@ namespace MediaBrowser.Model.ApiClient
/// <summary>
/// Gets an image url that can be used to download an image from the api
/// </summary>
- /// <param name="name">The name of the person</param>
- /// <param name="options">The options.</param>
- /// <returns>System.String.</returns>
- /// <exception cref="ArgumentNullException">name</exception>
- string GetPersonImageUrl(string name, ImageOptions options);
-
- /// <summary>
- /// Gets an image url that can be used to download an image from the api
- /// </summary>
/// <param name="year">The year.</param>
/// <param name="options">The options.</param>
/// <returns>System.String.</returns>
@@ -1310,15 +1317,6 @@ namespace MediaBrowser.Model.ApiClient
Task<QueryResult<BaseItemDto>> GetPlaylistItems(PlaylistItemQuery query);
/// <summary>
- /// Gets the url needed to stream an audio file
- /// </summary>
- /// <param name="options">The options.</param>
- /// <returns>System.String.</returns>
- /// <exception cref="ArgumentNullException">options</exception>
- [Obsolete]
- string GetAudioStreamUrl(StreamOptions options);
-
- /// <summary>
/// Gets the url needed to stream a video file
/// </summary>
/// <param name="options">The options.</param>
@@ -1421,5 +1419,26 @@ namespace MediaBrowser.Model.ApiClient
/// <param name="webSocketFactory">The web socket factory.</param>
/// <param name="keepAliveTimerMs">The keep alive timer ms.</param>
void OpenWebSocket(Func<IClientWebSocket> webSocketFactory, int keepAliveTimerMs = 60000);
+
+ /// <summary>
+ /// Reports the offline actions.
+ /// </summary>
+ /// <param name="actions">The actions.</param>
+ /// <returns>Task.</returns>
+ Task ReportOfflineActions(List<UserAction> actions);
+
+ /// <summary>
+ /// Gets the ready synchronize items.
+ /// </summary>
+ /// <param name="targetId">The target identifier.</param>
+ /// <returns>List&lt;SyncedItem&gt;.</returns>
+ Task<List<SyncedItem>> GetReadySyncItems(string targetId);
+
+ /// <summary>
+ /// Synchronizes the data.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <returns>Task&lt;SyncDataResponse&gt;.</returns>
+ Task<SyncDataResponse> SyncData(SyncDataRequest request);
}
} \ No newline at end of file
diff --git a/MediaBrowser.Model/Configuration/CinemaModeConfiguration.cs b/MediaBrowser.Model/Configuration/CinemaModeConfiguration.cs
index bd20713de..764a7222f 100644
--- a/MediaBrowser.Model/Configuration/CinemaModeConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/CinemaModeConfiguration.cs
@@ -19,6 +19,7 @@ namespace MediaBrowser.Model.Configuration
public CinemaModeConfiguration()
{
EnableIntrosParentalControl = true;
+ EnableIntrosFromSimilarMovies = true;
TrailerLimit = 2;
}
}
diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs
new file mode 100644
index 000000000..f24367298
--- /dev/null
+++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs
@@ -0,0 +1,19 @@
+
+namespace MediaBrowser.Model.Configuration
+{
+ public class EncodingOptions
+ {
+ public EncodingQuality EncodingQuality { get; set; }
+ public string TranscodingTempPath { get; set; }
+ public double DownMixAudioBoost { get; set; }
+ public string H264Encoder { get; set; }
+ public bool EnableDebugLogging { get; set; }
+
+ public EncodingOptions()
+ {
+ H264Encoder = "libx264";
+ DownMixAudioBoost = 2;
+ EncodingQuality = EncodingQuality.Auto;
+ }
+ }
+}
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index b9eaf7001..6c941e804 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.Configuration
{
@@ -32,6 +33,12 @@ namespace MediaBrowser.Model.Configuration
public bool EnableInternetProviders { get; set; }
/// <summary>
+ /// Gets or sets a value indicating whether this instance is port authorized.
+ /// </summary>
+ /// <value><c>true</c> if this instance is port authorized; otherwise, <c>false</c>.</value>
+ public bool IsPortAuthorized { get; set; }
+
+ /// <summary>
/// Gets or sets the item by name path.
/// </summary>
/// <value>The item by name path.</value>
@@ -144,21 +151,8 @@ namespace MediaBrowser.Model.Configuration
/// <value>The image saving convention.</value>
public ImageSavingConvention ImageSavingConvention { get; set; }
- /// <summary>
- /// Gets or sets a value indicating whether [enable people prefix sub folders].
- /// </summary>
- /// <value><c>true</c> if [enable people prefix sub folders]; otherwise, <c>false</c>.</value>
- public bool EnablePeoplePrefixSubFolders { get; set; }
-
- /// <summary>
- /// Gets or sets the encoding quality.
- /// </summary>
- /// <value>The encoding quality.</value>
- public EncodingQuality MediaEncodingQuality { get; set; }
-
public MetadataOptions[] MetadataOptions { get; set; }
- public bool EnableDebugEncodingLogging { get; set; }
public string TranscodingTempPath { get; set; }
public bool EnableAutomaticRestart { get; set; }
@@ -171,16 +165,15 @@ namespace MediaBrowser.Model.Configuration
public string UICulture { get; set; }
- public double DownMixAudioBoost { get; set; }
-
public PeopleMetadataOptions PeopleMetadataOptions { get; set; }
public bool FindInternetTrailers { get; set; }
public string[] InsecureApps7 { get; set; }
public bool SaveMetadataHidden { get; set; }
+ public bool EnableWin8HttpListener { get; set; }
- public bool PlaylistImagesDeleted { get; set; }
+ public NameValuePair[] ContentTypes { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
@@ -188,17 +181,15 @@ namespace MediaBrowser.Model.Configuration
public ServerConfiguration()
: base()
{
- MediaEncodingQuality = EncodingQuality.Auto;
ImageSavingConvention = ImageSavingConvention.Compatible;
PublicPort = 8096;
HttpServerPortNumber = 8096;
EnableDashboardResponseCaching = true;
EnableAutomaticRestart = true;
- EnablePeoplePrefixSubFolders = true;
+ EnableWin8HttpListener = true;
EnableUPnP = true;
- DownMixAudioBoost = 2;
MinResumePct = 5;
MaxResumePct = 90;
@@ -212,6 +203,7 @@ namespace MediaBrowser.Model.Configuration
FindInternetTrailers = true;
PathSubstitutions = new PathSubstitution[] { };
+ ContentTypes = new NameValuePair[] { };
PreferredMetadataLanguage = "en";
MetadataCountryCode = "US";
diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs
index f0a27a2b9..9e33c1c36 100644
--- a/MediaBrowser.Model/Configuration/UserConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs
@@ -42,6 +42,10 @@ namespace MediaBrowser.Model.Configuration
/// <value><c>true</c> if this instance is hidden; otherwise, <c>false</c>.</value>
public bool IsHidden { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is disabled.
+ /// </summary>
+ /// <value><c>true</c> if this instance is disabled; otherwise, <c>false</c>.</value>
public bool IsDisabled { get; set; }
public bool DisplayMissingEpisodes { get; set; }
@@ -74,9 +78,6 @@ namespace MediaBrowser.Model.Configuration
public string[] OrderedViews { get; set; }
- public bool SyncConnectName { get; set; }
- public bool SyncConnectImage { get; set; }
-
public bool IncludeTrailersInSuggestions { get; set; }
public bool EnableCinemaMode { get; set; }
@@ -87,7 +88,9 @@ namespace MediaBrowser.Model.Configuration
public string[] LatestItemsExcludes { get; set; }
public string[] BlockedTags { get; set; }
-
+
+ public bool HasMigratedToPolicy { get; set; }
+
/// <summary>
/// Initializes a new instance of the <see cref="UserConfiguration" /> class.
/// </summary>
@@ -110,8 +113,6 @@ namespace MediaBrowser.Model.Configuration
ExcludeFoldersFromGrouping = new string[] { };
DisplayCollectionsView = true;
- SyncConnectName = true;
- SyncConnectImage = true;
IncludeTrailersInSuggestions = true;
EnableCinemaMode = true;
EnableUserPreferenceAccess = true;
diff --git a/MediaBrowser.Model/Devices/DeviceQuery.cs b/MediaBrowser.Model/Devices/DeviceQuery.cs
index c3b4313f4..2cd2389d8 100644
--- a/MediaBrowser.Model/Devices/DeviceQuery.cs
+++ b/MediaBrowser.Model/Devices/DeviceQuery.cs
@@ -18,5 +18,10 @@ namespace MediaBrowser.Model.Devices
/// </summary>
/// <value><c>null</c> if [supports synchronize] contains no value, <c>true</c> if [supports synchronize]; otherwise, <c>false</c>.</value>
public bool? SupportsSync { get; set; }
+ /// <summary>
+ /// Gets or sets the user identifier.
+ /// </summary>
+ /// <value>The user identifier.</value>
+ public string UserId { get; set; }
}
}
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index 208ea1420..7c47b0d44 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -109,7 +109,8 @@ namespace MediaBrowser.Model.Dlna
ItemId = options.ItemId,
MediaType = DlnaProfileType.Audio,
MediaSource = item,
- RunTimeTicks = item.RunTimeTicks
+ RunTimeTicks = item.RunTimeTicks,
+ Context = options.Context
};
int? maxBitrateSetting = options.GetMaxBitrate();
@@ -240,7 +241,8 @@ namespace MediaBrowser.Model.Dlna
ItemId = options.ItemId,
MediaType = DlnaProfileType.Video,
MediaSource = item,
- RunTimeTicks = item.RunTimeTicks
+ RunTimeTicks = item.RunTimeTicks,
+ Context = options.Context
};
int? audioStreamIndex = options.AudioStreamIndex ?? item.DefaultAudioStreamIndex;
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index 7ca8ab6b0..22eb0cf6c 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -18,6 +18,7 @@ namespace MediaBrowser.Model.Dlna
public string ItemId { get; set; }
public PlayMethod PlayMethod { get; set; }
+ public EncodingContext Context { get; set; }
public DlnaProfileType MediaType { get; set; }
@@ -374,9 +375,17 @@ namespace MediaBrowser.Model.Dlna
MediaStream stream = TargetAudioStream;
int? streamChannels = stream == null ? null : stream.Channels;
- return MaxAudioChannels.HasValue && !IsDirectStream
- ? (streamChannels.HasValue ? Math.Min(MaxAudioChannels.Value, streamChannels.Value) : MaxAudioChannels.Value)
- : streamChannels;
+ if (MaxAudioChannels.HasValue && !IsDirectStream)
+ {
+ if (streamChannels.HasValue)
+ {
+ return Math.Min(MaxAudioChannels.Value, streamChannels.Value);
+ }
+
+ return MaxAudioChannels.Value;
+ }
+
+ return streamChannels;
}
}
diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs
index 71cefa076..2383d4809 100644
--- a/MediaBrowser.Model/Dto/BaseItemDto.cs
+++ b/MediaBrowser.Model/Dto/BaseItemDto.cs
@@ -366,6 +366,11 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The user data.</value>
public UserItemDataDto UserData { get; set; }
+ /// <summary>
+ /// Gets or sets the season user data.
+ /// </summary>
+ /// <value>The season user data.</value>
+ public UserItemDataDto SeasonUserData { get; set; }
/// <summary>
/// Gets or sets the recursive item count.
@@ -549,7 +554,13 @@ namespace MediaBrowser.Model.Dto
/// Gets or sets a value indicating whether [supports playlists].
/// </summary>
/// <value><c>true</c> if [supports playlists]; otherwise, <c>false</c>.</value>
- public bool SupportsPlaylists { get; set; }
+ public bool SupportsPlaylists
+ {
+ get
+ {
+ return RunTimeTicks.HasValue || IsFolder || IsGenre || IsMusicGenre || IsArtist;
+ }
+ }
/// <summary>
/// Determines whether the specified type is type.
diff --git a/MediaBrowser.Model/Dto/MetadataEditorInfo.cs b/MediaBrowser.Model/Dto/MetadataEditorInfo.cs
new file mode 100644
index 000000000..9bd15fc8f
--- /dev/null
+++ b/MediaBrowser.Model/Dto/MetadataEditorInfo.cs
@@ -0,0 +1,27 @@
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Globalization;
+using MediaBrowser.Model.Providers;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Model.Dto
+{
+ public class MetadataEditorInfo
+ {
+ public List<ParentalRating> ParentalRatingOptions { get; set; }
+ public List<CountryInfo> Countries { get; set; }
+ public List<CultureDto> Cultures { get; set; }
+ public List<ExternalIdInfo> ExternalIdInfos { get; set; }
+
+ public string ContentType { get; set; }
+ public List<NameValuePair> ContentTypeOptions { get; set; }
+
+ public MetadataEditorInfo()
+ {
+ ParentalRatingOptions = new List<ParentalRating>();
+ Countries = new List<CountryInfo>();
+ Cultures = new List<CultureDto>();
+ ExternalIdInfos = new List<ExternalIdInfo>();
+ ContentTypeOptions = new List<NameValuePair>();
+ }
+ }
+}
diff --git a/MediaBrowser.Model/Dto/NameValuePair.cs b/MediaBrowser.Model/Dto/NameValuePair.cs
new file mode 100644
index 000000000..2d55e8f2a
--- /dev/null
+++ b/MediaBrowser.Model/Dto/NameValuePair.cs
@@ -0,0 +1,17 @@
+
+namespace MediaBrowser.Model.Dto
+{
+ public class NameValuePair
+ {
+ /// <summary>
+ /// Gets or sets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name { get; set; }
+ /// <summary>
+ /// Gets or sets the value.
+ /// </summary>
+ /// <value>The value.</value>
+ public string Value { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/Dto/StreamOptions.cs b/MediaBrowser.Model/Dto/StreamOptions.cs
deleted file mode 100644
index 5b7cdc6fb..000000000
--- a/MediaBrowser.Model/Dto/StreamOptions.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-using System;
-
-namespace MediaBrowser.Model.Dto
-{
- /// <summary>
- /// Class StreamOptions
- /// </summary>
- [Obsolete]
- public class StreamOptions
- {
- /// <summary>
- /// Gets or sets the audio bit rate.
- /// </summary>
- /// <value>The audio bit rate.</value>
- public int? AudioBitRate { get; set; }
-
- /// <summary>
- /// Gets or sets the audio codec.
- /// Omit to copy the original stream
- /// </summary>
- /// <value>The audio encoding format.</value>
- public string AudioCodec { get; set; }
-
- /// <summary>
- /// Gets or sets the item id.
- /// </summary>
- /// <value>The item id.</value>
- public string ItemId { get; set; }
-
- /// <summary>
- /// Gets or sets the max audio channels.
- /// </summary>
- /// <value>The max audio channels.</value>
- public int? MaxAudioChannels { get; set; }
-
- /// <summary>
- /// Gets or sets the max audio sample rate.
- /// </summary>
- /// <value>The max audio sample rate.</value>
- public int? MaxAudioSampleRate { get; set; }
-
- /// <summary>
- /// Gets or sets the start time ticks.
- /// </summary>
- /// <value>The start time ticks.</value>
- public long? StartTimeTicks { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether the original media should be served statically
- /// Only used with progressive streaming
- /// </summary>
- /// <value><c>true</c> if static; otherwise, <c>false</c>.</value>
- public bool? Static { get; set; }
-
- /// <summary>
- /// Gets or sets the output file extension.
- /// </summary>
- /// <value>The output file extension.</value>
- public string OutputFileExtension { get; set; }
-
- /// <summary>
- /// Gets or sets the device id.
- /// </summary>
- /// <value>The device id.</value>
- public string DeviceId { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Dto/VideoStreamOptions.cs b/MediaBrowser.Model/Dto/VideoStreamOptions.cs
index 606e928f2..e9a83bd12 100644
--- a/MediaBrowser.Model/Dto/VideoStreamOptions.cs
+++ b/MediaBrowser.Model/Dto/VideoStreamOptions.cs
@@ -6,9 +6,65 @@ namespace MediaBrowser.Model.Dto
/// Class VideoStreamOptions
/// </summary>
[Obsolete]
- public class VideoStreamOptions : StreamOptions
+ public class VideoStreamOptions
{
/// <summary>
+ /// Gets or sets the audio bit rate.
+ /// </summary>
+ /// <value>The audio bit rate.</value>
+ public int? AudioBitRate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the audio codec.
+ /// Omit to copy the original stream
+ /// </summary>
+ /// <value>The audio encoding format.</value>
+ public string AudioCodec { get; set; }
+
+ /// <summary>
+ /// Gets or sets the item id.
+ /// </summary>
+ /// <value>The item id.</value>
+ public string ItemId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the max audio channels.
+ /// </summary>
+ /// <value>The max audio channels.</value>
+ public int? MaxAudioChannels { get; set; }
+
+ /// <summary>
+ /// Gets or sets the max audio sample rate.
+ /// </summary>
+ /// <value>The max audio sample rate.</value>
+ public int? MaxAudioSampleRate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the start time ticks.
+ /// </summary>
+ /// <value>The start time ticks.</value>
+ public long? StartTimeTicks { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the original media should be served statically
+ /// Only used with progressive streaming
+ /// </summary>
+ /// <value><c>true</c> if static; otherwise, <c>false</c>.</value>
+ public bool? Static { get; set; }
+
+ /// <summary>
+ /// Gets or sets the output file extension.
+ /// </summary>
+ /// <value>The output file extension.</value>
+ public string OutputFileExtension { get; set; }
+
+ /// <summary>
+ /// Gets or sets the device id.
+ /// </summary>
+ /// <value>The device id.</value>
+ public string DeviceId { get; set; }
+
+ /// <summary>
/// Gets or sets the video codec.
/// Omit to copy
/// </summary>
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index b81b4c1fd..2a1b0b659 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -96,6 +96,7 @@
<Compile Include="Configuration\ChannelOptions.cs" />
<Compile Include="Configuration\ChapterOptions.cs" />
<Compile Include="Configuration\CinemaModeConfiguration.cs" />
+ <Compile Include="Configuration\EncodingOptions.cs" />
<Compile Include="Configuration\MetadataConfiguration.cs" />
<Compile Include="Configuration\PeopleMetadataOptions.cs" />
<Compile Include="Configuration\XbmcMetadataOptions.cs" />
@@ -126,11 +127,11 @@
<Compile Include="Dlna\SubtitleDeliveryMethod.cs" />
<Compile Include="Dlna\SubtitleStreamInfo.cs" />
<Compile Include="Drawing\ImageOrientation.cs" />
- <Compile Include="Dto\DtoOptions.cs" />
<Compile Include="Dto\IHasServerId.cs" />
+ <Compile Include="Dto\MetadataEditorInfo.cs" />
+ <Compile Include="Dto\NameValuePair.cs" />
<Compile Include="MediaInfo\LiveMediaInfoResult.cs" />
<Compile Include="Dto\MediaSourceType.cs" />
- <Compile Include="Dto\StreamOptions.cs" />
<Compile Include="Dto\VideoStreamOptions.cs" />
<Compile Include="Configuration\DynamicDayOfWeek.cs" />
<Compile Include="Entities\ExtraType.cs" />
@@ -152,6 +153,8 @@
<Compile Include="Configuration\MetadataPluginType.cs" />
<Compile Include="Dlna\SubtitleProfile.cs" />
<Compile Include="MediaInfo\MediaProtocol.cs" />
+ <Compile Include="Net\HttpResponse.cs" />
+ <Compile Include="Net\MimeTypes.cs" />
<Compile Include="Notifications\NotificationOption.cs" />
<Compile Include="Notifications\NotificationOptions.cs" />
<Compile Include="Notifications\NotificationType.cs" />
@@ -363,8 +366,15 @@
<Compile Include="Session\TranscodingInfo.cs" />
<Compile Include="Session\UserDataChangeInfo.cs" />
<Compile Include="Devices\ContentUploadHistory.cs" />
+ <Compile Include="Sync\DeviceFileInfo.cs" />
+ <Compile Include="Sync\ItemFIleInfo.cs" />
+ <Compile Include="Sync\ItemFileType.cs" />
+ <Compile Include="Sync\LocalItem.cs" />
<Compile Include="Sync\SyncCategory.cs" />
+ <Compile Include="Sync\SyncDataRequest.cs" />
+ <Compile Include="Sync\SyncDataResponse.cs" />
<Compile Include="Sync\SyncDialogOptions.cs" />
+ <Compile Include="Sync\SyncedItem.cs" />
<Compile Include="Sync\SyncHelper.cs" />
<Compile Include="Sync\SyncJob.cs" />
<Compile Include="Sync\SyncJobCreationResult.cs" />
@@ -419,6 +429,8 @@
<Compile Include="Users\ForgotPasswordAction.cs" />
<Compile Include="Users\ForgotPasswordResult.cs" />
<Compile Include="Users\PinRedeemResult.cs" />
+ <Compile Include="Users\UserAction.cs" />
+ <Compile Include="Users\UserActionType.cs" />
<Compile Include="Users\UserPolicy.cs" />
<None Include="Fody.targets" />
<None Include="FodyWeavers.xml" />
diff --git a/MediaBrowser.Model/Net/HttpResponse.cs b/MediaBrowser.Model/Net/HttpResponse.cs
new file mode 100644
index 000000000..f4bd8e681
--- /dev/null
+++ b/MediaBrowser.Model/Net/HttpResponse.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+
+namespace MediaBrowser.Model.Net
+{
+ public class HttpResponse : IDisposable
+ {
+ /// <summary>
+ /// Gets or sets the type of the content.
+ /// </summary>
+ /// <value>The type of the content.</value>
+ public string ContentType { get; set; }
+
+ /// <summary>
+ /// Gets or sets the response URL.
+ /// </summary>
+ /// <value>The response URL.</value>
+ public string ResponseUrl { get; set; }
+
+ /// <summary>
+ /// Gets or sets the content.
+ /// </summary>
+ /// <value>The content.</value>
+ public Stream Content { get; set; }
+
+ /// <summary>
+ /// Gets or sets the status code.
+ /// </summary>
+ /// <value>The status code.</value>
+ public HttpStatusCode StatusCode { get; set; }
+
+ /// <summary>
+ /// Gets or sets the length of the content.
+ /// </summary>
+ /// <value>The length of the content.</value>
+ public long? ContentLength { get; set; }
+
+ /// <summary>
+ /// Gets or sets the headers.
+ /// </summary>
+ /// <value>The headers.</value>
+ public Dictionary<string, string> Headers { get; set; }
+
+ private readonly IDisposable _disposable;
+
+ public HttpResponse(IDisposable disposable)
+ {
+ _disposable = disposable;
+ }
+ public HttpResponse()
+ {
+ }
+
+ public void Dispose()
+ {
+ if (_disposable != null)
+ {
+ _disposable.Dispose();
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs
index 14052e759..1f54e48d1 100644
--- a/MediaBrowser.Common/Net/MimeTypes.cs
+++ b/MediaBrowser.Model/Net/MimeTypes.cs
@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
-namespace MediaBrowser.Common.Net
+namespace MediaBrowser.Model.Net
{
/// <summary>
/// Class MimeTypes
@@ -11,11 +11,6 @@ namespace MediaBrowser.Common.Net
public static class MimeTypes
{
/// <summary>
- /// The json MIME type
- /// </summary>
- public static string JsonMimeType = "application/json";
-
- /// <summary>
/// Any extension in this list is considered a video file - can be added to at runtime for extensibility
/// </summary>
private static readonly List<string> VideoFileExtensions = new List<string>
@@ -52,26 +47,43 @@ namespace MediaBrowser.Common.Net
private static readonly Dictionary<string, string> VideoFileExtensionsDictionary = VideoFileExtensions.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
- /// <summary>
- /// Determines whether [is video file] [the specified path].
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns><c>true</c> if [is video file] [the specified path]; otherwise, <c>false</c>.</returns>
- public static bool IsVideoFile(string path)
- {
- if (string.IsNullOrEmpty(path))
+ // http://en.wikipedia.org/wiki/Internet_media_type
+ // Add more as needed
+
+ private static readonly Dictionary<string, string> MimeTypeLookup =
+ new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
- throw new ArgumentNullException("path");
- }
+ {".jpg", "image/jpeg"},
+ {".jpeg", "image/jpeg"},
+ {".tbn", "image/jpeg"},
+ {".png", "image/png"},
+ {".gif", "image/gif"},
+ {".webp", "image/webp"},
+ {".ico", "image/vnd.microsoft.icon"},
+ {".mpg", "video/mpeg"},
+ {".mpeg", "video/mpeg"},
+ {".ogv", "video/ogg"},
+ {".mov", "video/quicktime"},
+ {".webm", "video/webm"},
+ {".mkv", "video/x-matroska"},
+ {".wmv", "video/x-ms-wmv"},
+ {".flv", "video/x-flv"},
+ {".avi", "video/x-msvideo"},
+ {".asf", "video/x-ms-asf"},
+ {".m4v", "video/x-m4v"}
+ };
- var extension = Path.GetExtension(path);
+ private static readonly Dictionary<string, string> ExtensionLookup = CreateExtensionLookup();
- if (string.IsNullOrEmpty(extension))
- {
- return false;
- }
+ private static Dictionary<string, string> CreateExtensionLookup()
+ {
+ var dict = MimeTypeLookup
+ .GroupBy(i => i.Value)
+ .ToDictionary(x => x.Key, x => x.First().Key, StringComparer.OrdinalIgnoreCase);
+
+ dict["image/jpg"] = ".jpg";
- return VideoFileExtensionsDictionary.ContainsKey(extension);
+ return dict;
}
/// <summary>
@@ -79,8 +91,8 @@ namespace MediaBrowser.Common.Net
/// </summary>
/// <param name="path">The path.</param>
/// <returns>System.String.</returns>
- /// <exception cref="System.ArgumentNullException">path</exception>
- /// <exception cref="System.InvalidOperationException">Argument not supported: + path</exception>
+ /// <exception cref="ArgumentNullException">path</exception>
+ /// <exception cref="InvalidOperationException">Argument not supported: + path</exception>
public static string GetMimeType(string path)
{
if (string.IsNullOrEmpty(path))
@@ -90,50 +102,13 @@ namespace MediaBrowser.Common.Net
var ext = Path.GetExtension(path) ?? string.Empty;
- // http://en.wikipedia.org/wiki/Internet_media_type
- // Add more as needed
-
- // Type video
- if (ext.Equals(".mpg", StringComparison.OrdinalIgnoreCase) || ext.EndsWith("mpeg", StringComparison.OrdinalIgnoreCase))
- {
- return "video/mpeg";
- }
- if (ext.Equals(".ogv", StringComparison.OrdinalIgnoreCase))
- {
- return "video/ogg";
- }
- if (ext.Equals(".mov", StringComparison.OrdinalIgnoreCase))
+ string result;
+ if (MimeTypeLookup.TryGetValue(ext, out result))
{
- return "video/quicktime";
- }
- if (ext.Equals(".webm", StringComparison.OrdinalIgnoreCase))
- {
- return "video/webm";
- }
- if (ext.Equals(".mkv", StringComparison.OrdinalIgnoreCase))
- {
- return "video/x-matroska";
- }
- if (ext.Equals(".wmv", StringComparison.OrdinalIgnoreCase))
- {
- return "video/x-ms-wmv";
- }
- if (ext.Equals(".flv", StringComparison.OrdinalIgnoreCase))
- {
- return "video/x-flv";
- }
- if (ext.Equals(".avi", StringComparison.OrdinalIgnoreCase))
- {
- return "video/x-msvideo";
- }
- if (ext.Equals(".m4v", StringComparison.OrdinalIgnoreCase))
- {
- return "video/x-m4v";
- }
- if (ext.EndsWith("asf", StringComparison.OrdinalIgnoreCase))
- {
- return "video/x-ms-asf";
+ return result;
}
+
+ // Type video
if (ext.Equals(".3gp", StringComparison.OrdinalIgnoreCase))
{
return "video/3gpp";
@@ -197,28 +172,6 @@ namespace MediaBrowser.Common.Net
return "application/x-cdisplay";
}
- // Type image
- if (ext.Equals(".gif", StringComparison.OrdinalIgnoreCase))
- {
- return "image/gif";
- }
- if (ext.Equals(".jpg", StringComparison.OrdinalIgnoreCase) || ext.Equals(".jpeg", StringComparison.OrdinalIgnoreCase) || ext.Equals(".tbn", StringComparison.OrdinalIgnoreCase))
- {
- return "image/jpeg";
- }
- if (ext.Equals(".png", StringComparison.OrdinalIgnoreCase))
- {
- return "image/png";
- }
- if (ext.Equals(".webp", StringComparison.OrdinalIgnoreCase))
- {
- return "image/webp";
- }
- if (ext.Equals(".ico", StringComparison.OrdinalIgnoreCase))
- {
- return "image/vnd.microsoft.icon";
- }
-
// Type audio
if (ext.Equals(".mp3", StringComparison.OrdinalIgnoreCase))
{
@@ -272,7 +225,7 @@ namespace MediaBrowser.Common.Net
}
if (ext.Equals(".json", StringComparison.OrdinalIgnoreCase))
{
- return JsonMimeType;
+ return "application/json";
}
if (ext.Equals(".map", StringComparison.OrdinalIgnoreCase))
{
@@ -320,19 +273,19 @@ namespace MediaBrowser.Common.Net
throw new ArgumentException("Argument not supported: " + path);
}
- private static readonly Dictionary<string, string> MimeExtensions =
- new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
- {
- {"image/jpeg", "jpg"},
- {"image/jpg", "jpg"},
- {"image/png", "png"},
- {"image/gif", "gif"},
- {"image/webp", "webp"}
- };
-
public static string ToExtension(string mimeType)
{
- return "." + MimeExtensions[mimeType];
+ if (string.IsNullOrEmpty(mimeType))
+ {
+ throw new ArgumentNullException("mimeType");
+ }
+
+ string result;
+ if (ExtensionLookup.TryGetValue(mimeType, out result))
+ {
+ return result;
+ }
+ throw new ArgumentNullException("Unable to determine extension for mimeType: " + mimeType);
}
}
}
diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs
index 19e30cd8a..5018f8e51 100644
--- a/MediaBrowser.Model/Querying/ItemFields.cs
+++ b/MediaBrowser.Model/Querying/ItemFields.cs
@@ -7,6 +7,11 @@ namespace MediaBrowser.Model.Querying
public enum ItemFields
{
/// <summary>
+ /// The air time
+ /// </summary>
+ AirTime,
+
+ /// <summary>
/// The alternate episode numbers
/// </summary>
AlternateEpisodeNumbers,
@@ -102,11 +107,6 @@ namespace MediaBrowser.Model.Querying
Metascore,
/// <summary>
- /// The metadata settings
- /// </summary>
- Settings,
-
- /// <summary>
/// The item overview
/// </summary>
Overview,
@@ -152,6 +152,16 @@ namespace MediaBrowser.Model.Querying
Revenue,
/// <summary>
+ /// The season name
+ /// </summary>
+ SeasonName,
+
+ /// <summary>
+ /// The settings
+ /// </summary>
+ Settings,
+
+ /// <summary>
/// The short overview
/// </summary>
ShortOverview,
@@ -182,6 +192,11 @@ namespace MediaBrowser.Model.Querying
SortName,
/// <summary>
+ /// The special episode numbers
+ /// </summary>
+ SpecialEpisodeNumbers,
+
+ /// <summary>
/// The studios of the item
/// </summary>
Studios,
@@ -219,6 +234,11 @@ namespace MediaBrowser.Model.Querying
/// <summary>
/// The media streams
/// </summary>
- MediaStreams
+ MediaStreams,
+
+ /// <summary>
+ /// The season user data
+ /// </summary>
+ SeasonUserData
}
}
diff --git a/MediaBrowser.Model/Session/TranscodingInfo.cs b/MediaBrowser.Model/Session/TranscodingInfo.cs
index b3ab32a44..e646d80d3 100644
--- a/MediaBrowser.Model/Session/TranscodingInfo.cs
+++ b/MediaBrowser.Model/Session/TranscodingInfo.cs
@@ -5,6 +5,8 @@ namespace MediaBrowser.Model.Session
public string AudioCodec { get; set; }
public string VideoCodec { get; set; }
public string Container { get; set; }
+ public bool IsVideoDirect { get; set; }
+ public bool IsAudioDirect { get; set; }
public int? Bitrate { get; set; }
public float? Framerate { get; set; }
diff --git a/MediaBrowser.Model/Sync/DeviceFileInfo.cs b/MediaBrowser.Model/Sync/DeviceFileInfo.cs
new file mode 100644
index 000000000..bc93b69bc
--- /dev/null
+++ b/MediaBrowser.Model/Sync/DeviceFileInfo.cs
@@ -0,0 +1,9 @@
+
+namespace MediaBrowser.Model.Sync
+{
+ public class DeviceFileInfo
+ {
+ public string Path { get; set; }
+ public string Name { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/Sync/ItemFIleInfo.cs b/MediaBrowser.Model/Sync/ItemFIleInfo.cs
new file mode 100644
index 000000000..b110af6b5
--- /dev/null
+++ b/MediaBrowser.Model/Sync/ItemFIleInfo.cs
@@ -0,0 +1,28 @@
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Model.Sync
+{
+ public class ItemFileInfo
+ {
+ /// <summary>
+ /// Gets or sets the type.
+ /// </summary>
+ /// <value>The type.</value>
+ public ItemFileType Type { get; set; }
+ /// <summary>
+ /// Gets or sets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name { get; set; }
+ /// <summary>
+ /// Gets or sets the path.
+ /// </summary>
+ /// <value>The path.</value>
+ public string Path { get; set; }
+ /// <summary>
+ /// Gets or sets the type of the image.
+ /// </summary>
+ /// <value>The type of the image.</value>
+ public ImageType ImageType { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/Sync/ItemFileType.cs b/MediaBrowser.Model/Sync/ItemFileType.cs
new file mode 100644
index 000000000..305f4c502
--- /dev/null
+++ b/MediaBrowser.Model/Sync/ItemFileType.cs
@@ -0,0 +1,19 @@
+
+namespace MediaBrowser.Model.Sync
+{
+ public enum ItemFileType
+ {
+ /// <summary>
+ /// The media
+ /// </summary>
+ Media = 0,
+ /// <summary>
+ /// The image
+ /// </summary>
+ Image = 1,
+ /// <summary>
+ /// The subtitles
+ /// </summary>
+ Subtitles = 2
+ }
+}
diff --git a/MediaBrowser.Model/Sync/LocalItem.cs b/MediaBrowser.Model/Sync/LocalItem.cs
new file mode 100644
index 000000000..ec4544524
--- /dev/null
+++ b/MediaBrowser.Model/Sync/LocalItem.cs
@@ -0,0 +1,33 @@
+using MediaBrowser.Model.Dto;
+
+namespace MediaBrowser.Model.Sync
+{
+ public class LocalItem
+ {
+ /// <summary>
+ /// Gets or sets the item.
+ /// </summary>
+ /// <value>The item.</value>
+ public BaseItemDto Item { get; set; }
+ /// <summary>
+ /// Gets or sets the local path.
+ /// </summary>
+ /// <value>The local path.</value>
+ public string LocalPath { get; set; }
+ /// <summary>
+ /// Gets or sets the server identifier.
+ /// </summary>
+ /// <value>The server identifier.</value>
+ public string ServerId { get; set; }
+ /// <summary>
+ /// Gets or sets the unique identifier.
+ /// </summary>
+ /// <value>The unique identifier.</value>
+ public string Id { get; set; }
+ /// <summary>
+ /// Gets or sets the item identifier.
+ /// </summary>
+ /// <value>The item identifier.</value>
+ public string ItemId { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/Sync/SyncDataRequest.cs b/MediaBrowser.Model/Sync/SyncDataRequest.cs
new file mode 100644
index 000000000..3eb447b3f
--- /dev/null
+++ b/MediaBrowser.Model/Sync/SyncDataRequest.cs
@@ -0,0 +1,16 @@
+using System.Collections.Generic;
+
+namespace MediaBrowser.Model.Sync
+{
+ public class SyncDataRequest
+ {
+ public List<string> LocalItemIds { get; set; }
+
+ public string TargetId { get; set; }
+
+ public SyncDataRequest()
+ {
+ LocalItemIds = new List<string>();
+ }
+ }
+}
diff --git a/MediaBrowser.Model/Sync/SyncDataResponse.cs b/MediaBrowser.Model/Sync/SyncDataResponse.cs
new file mode 100644
index 000000000..ac7ff5c84
--- /dev/null
+++ b/MediaBrowser.Model/Sync/SyncDataResponse.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+
+namespace MediaBrowser.Model.Sync
+{
+ public class SyncDataResponse
+ {
+ public List<string> ItemIdsToRemove { get; set; }
+
+ public SyncDataResponse()
+ {
+ ItemIdsToRemove = new List<string>();
+ }
+ }
+}
diff --git a/MediaBrowser.Model/Sync/SyncHelper.cs b/MediaBrowser.Model/Sync/SyncHelper.cs
index 28a36ed21..c2a446fbe 100644
--- a/MediaBrowser.Model/Sync/SyncHelper.cs
+++ b/MediaBrowser.Model/Sync/SyncHelper.cs
@@ -62,6 +62,7 @@ namespace MediaBrowser.Model.Sync
{
List<SyncOptions> options = new List<SyncOptions>();
+ options.Add(SyncOptions.Name);
options.Add(SyncOptions.Quality);
options.Add(SyncOptions.UnwatchedOnly);
options.Add(SyncOptions.SyncNewContent);
diff --git a/MediaBrowser.Model/Sync/SyncJob.cs b/MediaBrowser.Model/Sync/SyncJob.cs
index 92662d7bb..24680d172 100644
--- a/MediaBrowser.Model/Sync/SyncJob.cs
+++ b/MediaBrowser.Model/Sync/SyncJob.cs
@@ -89,7 +89,6 @@ namespace MediaBrowser.Model.Sync
public string ParentName { get; set; }
public string PrimaryImageItemId { get; set; }
public string PrimaryImageTag { get; set; }
- public double? PrimaryImageAspectRatio { get; set; }
public SyncJob()
{
diff --git a/MediaBrowser.Model/Sync/SyncJobItem.cs b/MediaBrowser.Model/Sync/SyncJobItem.cs
index 063f7feb2..4090d82b0 100644
--- a/MediaBrowser.Model/Sync/SyncJobItem.cs
+++ b/MediaBrowser.Model/Sync/SyncJobItem.cs
@@ -23,6 +23,18 @@ namespace MediaBrowser.Model.Sync
public string ItemId { get; set; }
/// <summary>
+ /// Gets or sets the name of the item.
+ /// </summary>
+ /// <value>The name of the item.</value>
+ public string ItemName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the media source identifier.
+ /// </summary>
+ /// <value>The media source identifier.</value>
+ public string MediaSourceId { get; set; }
+
+ /// <summary>
/// Gets or sets the target identifier.
/// </summary>
/// <value>The target identifier.</value>
@@ -51,5 +63,15 @@ namespace MediaBrowser.Model.Sync
/// </summary>
/// <value>The date created.</value>
public DateTime DateCreated { get; set; }
+ /// <summary>
+ /// Gets or sets the primary image item identifier.
+ /// </summary>
+ /// <value>The primary image item identifier.</value>
+ public string PrimaryImageItemId { get; set; }
+ /// <summary>
+ /// Gets or sets the primary image tag.
+ /// </summary>
+ /// <value>The primary image tag.</value>
+ public string PrimaryImageTag { get; set; }
}
}
diff --git a/MediaBrowser.Model/Sync/SyncJobItemQuery.cs b/MediaBrowser.Model/Sync/SyncJobItemQuery.cs
index b85c21691..d21138204 100644
--- a/MediaBrowser.Model/Sync/SyncJobItemQuery.cs
+++ b/MediaBrowser.Model/Sync/SyncJobItemQuery.cs
@@ -1,4 +1,5 @@
-
+using System.Collections.Generic;
+
namespace MediaBrowser.Model.Sync
{
public class SyncJobItemQuery
@@ -27,11 +28,16 @@ namespace MediaBrowser.Model.Sync
/// Gets or sets the status.
/// </summary>
/// <value>The status.</value>
- public SyncJobItemStatus? Status { get; set; }
+ public List<SyncJobItemStatus> Statuses { get; set; }
/// <summary>
- /// Gets or sets a value indicating whether this instance is completed.
+ /// Gets or sets a value indicating whether [add metadata].
/// </summary>
- /// <value><c>null</c> if [is completed] contains no value, <c>true</c> if [is completed]; otherwise, <c>false</c>.</value>
- public bool? IsCompleted { get; set; }
+ /// <value><c>true</c> if [add metadata]; otherwise, <c>false</c>.</value>
+ public bool AddMetadata { get; set; }
+
+ public SyncJobItemQuery()
+ {
+ Statuses = new List<SyncJobItemStatus>();
+ }
}
}
diff --git a/MediaBrowser.Model/Sync/SyncJobItemStatus.cs b/MediaBrowser.Model/Sync/SyncJobItemStatus.cs
index 3d0579a3c..913f9e259 100644
--- a/MediaBrowser.Model/Sync/SyncJobItemStatus.cs
+++ b/MediaBrowser.Model/Sync/SyncJobItemStatus.cs
@@ -6,7 +6,9 @@ namespace MediaBrowser.Model.Sync
Queued = 0,
Converting = 1,
Transferring = 2,
- Completed = 3,
- Failed = 4
+ Synced = 3,
+ RemovedFromDevice = 4,
+ Failed = 5,
+ Cancelled = 6
}
}
diff --git a/MediaBrowser.Model/Sync/SyncJobQuery.cs b/MediaBrowser.Model/Sync/SyncJobQuery.cs
index 2af06bcfa..35f0e076d 100644
--- a/MediaBrowser.Model/Sync/SyncJobQuery.cs
+++ b/MediaBrowser.Model/Sync/SyncJobQuery.cs
@@ -23,5 +23,10 @@ namespace MediaBrowser.Model.Sync
/// </summary>
/// <value>The target identifier.</value>
public string TargetId { get; set; }
+ /// <summary>
+ /// Gets or sets the user identifier.
+ /// </summary>
+ /// <value>The user identifier.</value>
+ public string UserId { get; set; }
}
}
diff --git a/MediaBrowser.Model/Sync/SyncQuality.cs b/MediaBrowser.Model/Sync/SyncQuality.cs
index f915e2768..d34ad22c2 100644
--- a/MediaBrowser.Model/Sync/SyncQuality.cs
+++ b/MediaBrowser.Model/Sync/SyncQuality.cs
@@ -6,16 +6,16 @@ namespace MediaBrowser.Model.Sync
/// <summary>
/// The good
/// </summary>
- Good = 0,
+ Low = 0,
/// <summary>
/// The better
/// </summary>
- Better = 1,
+ Medium = 1,
/// <summary>
/// The best
/// </summary>
- Best = 2
+ High = 2
}
}
diff --git a/MediaBrowser.Model/Sync/SyncedItem.cs b/MediaBrowser.Model/Sync/SyncedItem.cs
new file mode 100644
index 000000000..784a12bc9
--- /dev/null
+++ b/MediaBrowser.Model/Sync/SyncedItem.cs
@@ -0,0 +1,38 @@
+using MediaBrowser.Model.Dto;
+
+namespace MediaBrowser.Model.Sync
+{
+ public class SyncedItem
+ {
+ /// <summary>
+ /// Gets or sets the server identifier.
+ /// </summary>
+ /// <value>The server identifier.</value>
+ public string ServerId { get; set; }
+ /// <summary>
+ /// Gets or sets the synchronize job identifier.
+ /// </summary>
+ /// <value>The synchronize job identifier.</value>
+ public string SyncJobId { get; set; }
+ /// <summary>
+ /// Gets or sets the synchronize job item identifier.
+ /// </summary>
+ /// <value>The synchronize job item identifier.</value>
+ public string SyncJobItemId { get; set; }
+ /// <summary>
+ /// Gets or sets the name of the original file.
+ /// </summary>
+ /// <value>The name of the original file.</value>
+ public string OriginalFileName { get; set; }
+ /// <summary>
+ /// Gets or sets the item.
+ /// </summary>
+ /// <value>The item.</value>
+ public BaseItemDto Item { get; set; }
+ /// <summary>
+ /// Gets or sets the user identifier.
+ /// </summary>
+ /// <value>The user identifier.</value>
+ public string UserId { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/Users/UserAction.cs b/MediaBrowser.Model/Users/UserAction.cs
new file mode 100644
index 000000000..680835364
--- /dev/null
+++ b/MediaBrowser.Model/Users/UserAction.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace MediaBrowser.Model.Users
+{
+ public class UserAction
+ {
+ public string Id { get; set; }
+ public string ServerId { get; set; }
+ public string UserId { get; set; }
+ public string ItemId { get; set; }
+ public UserActionType Type { get; set; }
+ public DateTime Date { get; set; }
+ public long? PositionTicks { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/Users/UserActionType.cs b/MediaBrowser.Model/Users/UserActionType.cs
new file mode 100644
index 000000000..493de6272
--- /dev/null
+++ b/MediaBrowser.Model/Users/UserActionType.cs
@@ -0,0 +1,8 @@
+
+namespace MediaBrowser.Model.Users
+{
+ public enum UserActionType
+ {
+ PlayedItem = 0
+ }
+}
diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs
index b458e2854..0a6a37696 100644
--- a/MediaBrowser.Model/Users/UserPolicy.cs
+++ b/MediaBrowser.Model/Users/UserPolicy.cs
@@ -1,8 +1,75 @@
-
+using MediaBrowser.Model.Configuration;
+
namespace MediaBrowser.Model.Users
{
public class UserPolicy
{
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is administrator.
+ /// </summary>
+ /// <value><c>true</c> if this instance is administrator; otherwise, <c>false</c>.</value>
+ public bool IsAdministrator { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is hidden.
+ /// </summary>
+ /// <value><c>true</c> if this instance is hidden; otherwise, <c>false</c>.</value>
+ public bool IsHidden { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is disabled.
+ /// </summary>
+ /// <value><c>true</c> if this instance is disabled; otherwise, <c>false</c>.</value>
+ public bool IsDisabled { get; set; }
+
+ /// <summary>
+ /// Gets or sets the max parental rating.
+ /// </summary>
+ /// <value>The max parental rating.</value>
+ public int? MaxParentalRating { get; set; }
+
+ public string[] BlockedTags { get; set; }
+ public bool EnableUserPreferenceAccess { get; set; }
+ public AccessSchedule[] AccessSchedules { get; set; }
+ public UnratedItem[] BlockUnratedItems { get; set; }
+ public string[] BlockedMediaFolders { get; set; }
+ public string[] BlockedChannels { get; set; }
+ public bool EnableRemoteControlOfOtherUsers { get; set; }
+ public bool EnableSharedDeviceControl { get; set; }
+
+ public bool EnableLiveTvManagement { get; set; }
+ public bool EnableLiveTvAccess { get; set; }
+
+ public bool EnableMediaPlayback { get; set; }
+ public bool EnableContentDeletion { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether [enable synchronize].
+ /// </summary>
+ /// <value><c>true</c> if [enable synchronize]; otherwise, <c>false</c>.</value>
public bool EnableSync { get; set; }
+
+ public string[] EnabledDevices { get; set; }
+ public bool EnableAllDevices { get; set; }
+
+ public UserPolicy()
+ {
+ EnableLiveTvManagement = true;
+ EnableMediaPlayback = true;
+ EnableLiveTvAccess = true;
+ EnableSharedDeviceControl = true;
+
+ BlockedMediaFolders = new string[] { };
+ BlockedTags = new string[] { };
+ BlockedChannels = new string[] { };
+ BlockUnratedItems = new UnratedItem[] { };
+
+ EnableUserPreferenceAccess = true;
+
+ AccessSchedules = new AccessSchedule[] { };
+
+ EnabledDevices = new string[] { };
+ EnableAllDevices = true;
+ }
}
}
diff --git a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
index 062519f9d..5afaaa875 100644
--- a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
+++ b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
@@ -75,7 +75,7 @@ namespace MediaBrowser.Providers.BoxSets
if (!string.Equals(currentOfficialRating ?? string.Empty, item.OfficialRating ?? string.Empty,
StringComparison.OrdinalIgnoreCase))
{
- updateType = updateType | ItemUpdateType.MetadataDownload;
+ updateType = updateType | ItemUpdateType.MetadataEdit;
}
}
diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs
index 2c97f2c16..700bca0fa 100644
--- a/MediaBrowser.Providers/Manager/ImageSaver.cs
+++ b/MediaBrowser.Providers/Manager/ImageSaver.cs
@@ -16,6 +16,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Model.Net;
namespace MediaBrowser.Providers.Manager
{
diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
index b0c1b10e4..65d8e287f 100644
--- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
@@ -6,7 +6,6 @@ using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
@@ -132,7 +131,9 @@ namespace MediaBrowser.Providers.MediaInfo
public bool Supports(IHasImages item)
{
- return item is Audio;
+ var audio = item as Audio;
+
+ return item.LocationType == LocationType.FileSystem && audio != null && !audio.IsArchive;
}
public bool HasChanged(IHasMetadata item, MetadataStatus status, IDirectoryService directoryService)
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
index a856d5dba..2634bd9bc 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
@@ -38,6 +38,13 @@ namespace MediaBrowser.Providers.MediaInfo
public async Task<ItemUpdateType> Probe<T>(T item, CancellationToken cancellationToken)
where T : Audio
{
+ if (item.IsArchive)
+ {
+ var ext = Path.GetExtension(item.Path) ?? string.Empty;
+ item.Container = ext.TrimStart('.');
+ return ItemUpdateType.MetadataImport;
+ }
+
var result = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
@@ -58,8 +65,8 @@ namespace MediaBrowser.Providers.MediaInfo
cancellationToken.ThrowIfCancellationRequested();
var idString = item.Id.ToString("N");
- var cachePath = Path.Combine(_appPaths.CachePath,
- "ffprobe-audio",
+ var cachePath = Path.Combine(_appPaths.CachePath,
+ "ffprobe-audio",
idString.Substring(0, 2), idString, "v" + SchemaVersion + _mediaEncoder.Version + item.DateModified.Ticks.ToString(_usCulture) + ".json");
try
@@ -132,7 +139,7 @@ namespace MediaBrowser.Providers.MediaInfo
if (!string.IsNullOrEmpty(data.format.size))
{
- audio.Size = long.Parse(data.format.size , _usCulture);
+ audio.Size = long.Parse(data.format.size, _usCulture);
}
else
{
@@ -217,9 +224,9 @@ namespace MediaBrowser.Providers.MediaInfo
audio.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date");
// Several different forms of retaildate
- audio.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ??
- FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ??
- FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ??
+ audio.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ??
+ FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ??
+ FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ??
FFProbeHelpers.GetDictionaryDateTime(tags, "date");
// If we don't have a ProductionYear try and get it from PremiereDate
@@ -261,8 +268,8 @@ namespace MediaBrowser.Providers.MediaInfo
{
// Only use the comma as a delimeter if there are no slashes or pipes.
// We want to be careful not to split names that have commas in them
- var delimeter = !allowCommaDelimiter || _nameDelimiters.Any(i => val.IndexOf(i) != -1) ?
- _nameDelimiters :
+ var delimeter = !allowCommaDelimiter || _nameDelimiters.Any(i => val.IndexOf(i) != -1) ?
+ _nameDelimiters :
new[] { ',' };
return val.Split(delimeter, StringSplitOptions.RemoveEmptyEntries)
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
index 7c078866a..7f025dea1 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
@@ -71,6 +71,13 @@ namespace MediaBrowser.Providers.MediaInfo
CancellationToken cancellationToken)
where T : Video
{
+ if (item.IsArchive)
+ {
+ var ext = Path.GetExtension(item.Path) ?? string.Empty;
+ item.Container = ext.TrimStart('.');
+ return ItemUpdateType.MetadataImport;
+ }
+
var isoMount = await MountIsoIfNeeded(item, cancellationToken).ConfigureAwait(false);
BlurayDiscInfo blurayDiscInfo = null;
diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
index a91a01edc..7f55ce1ac 100644
--- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
@@ -123,7 +123,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
var video = item as Video;
- return item.LocationType == LocationType.FileSystem && video != null && !video.IsPlaceHolder && !video.IsShortcut;
+ return item.LocationType == LocationType.FileSystem && video != null && !video.IsPlaceHolder && !video.IsShortcut && !video.IsArchive;
}
public int Order
diff --git a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs
index a7ccf3f6e..6813f2ff5 100644
--- a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs
+++ b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs
@@ -106,6 +106,10 @@ namespace MediaBrowser.Providers.Movies
{
// No biggie. Don't blow up
}
+ catch (DirectoryNotFoundException)
+ {
+ // No biggie. Don't blow up
+ }
}
var language = item.GetPreferredMetadataLanguage();
diff --git a/MediaBrowser.Providers/Music/AlbumMetadataService.cs b/MediaBrowser.Providers/Music/AlbumMetadataService.cs
index 79fab2f70..6e3a5bf06 100644
--- a/MediaBrowser.Providers/Music/AlbumMetadataService.cs
+++ b/MediaBrowser.Providers/Music/AlbumMetadataService.cs
@@ -54,7 +54,7 @@ namespace MediaBrowser.Providers.Music
if (currentList.Count != item.Genres.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
{
- updateType = updateType | ItemUpdateType.MetadataDownload;
+ updateType = updateType | ItemUpdateType.MetadataEdit;
}
}
@@ -68,7 +68,7 @@ namespace MediaBrowser.Providers.Music
if (currentList.Count != item.Studios.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Studios.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
{
- updateType = updateType | ItemUpdateType.MetadataDownload;
+ updateType = updateType | ItemUpdateType.MetadataEdit;
}
}
@@ -81,15 +81,15 @@ namespace MediaBrowser.Providers.Music
if (!string.Equals(item.Name, name, StringComparison.Ordinal))
{
item.Name = name;
- updateType = updateType | ItemUpdateType.MetadataDownload;
+ updateType = updateType | ItemUpdateType.MetadataEdit;
}
}
}
- }
- updateType = updateType | SetAlbumArtistFromSongs(item, songs);
- updateType = updateType | SetArtistsFromSongs(item, songs);
- updateType = updateType | SetDateFromSongs(item, songs);
+ updateType = updateType | SetAlbumArtistFromSongs(item, songs);
+ updateType = updateType | SetArtistsFromSongs(item, songs);
+ updateType = updateType | SetDateFromSongs(item, songs);
+ }
return updateType;
}
@@ -106,7 +106,7 @@ namespace MediaBrowser.Providers.Music
if (!item.AlbumArtists.SequenceEqual(albumArtists, StringComparer.OrdinalIgnoreCase))
{
item.AlbumArtists = albumArtists;
- updateType = updateType | ItemUpdateType.MetadataDownload;
+ updateType = updateType | ItemUpdateType.MetadataEdit;
}
return updateType;
@@ -124,7 +124,7 @@ namespace MediaBrowser.Providers.Music
if (currentList.Count != item.Artists.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Artists.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
{
- updateType = updateType | ItemUpdateType.MetadataDownload;
+ updateType = updateType | ItemUpdateType.MetadataEdit;
}
return updateType;
@@ -158,7 +158,7 @@ namespace MediaBrowser.Providers.Music
if ((originalPremiereDate ?? DateTime.MinValue) != (item.PremiereDate ?? DateTime.MinValue) ||
(originalProductionYear ?? -1) != (item.ProductionYear ?? -1))
{
- updateType = updateType | ItemUpdateType.MetadataDownload;
+ updateType = updateType | ItemUpdateType.MetadataEdit;
}
return updateType;
diff --git a/MediaBrowser.Providers/Music/ArtistMetadataService.cs b/MediaBrowser.Providers/Music/ArtistMetadataService.cs
index 939bd2b3b..a73d82992 100644
--- a/MediaBrowser.Providers/Music/ArtistMetadataService.cs
+++ b/MediaBrowser.Providers/Music/ArtistMetadataService.cs
@@ -35,19 +35,22 @@ namespace MediaBrowser.Providers.Music
{
var updateType = base.BeforeSave(item);
- if (!item.IsAccessedByName && !item.LockedFields.Contains(MetadataFields.Genres))
+ if (!item.IsAccessedByName && !item.IsLocked)
{
- var songs = item.RecursiveChildren.OfType<Audio>().ToList();
+ if (!item.LockedFields.Contains(MetadataFields.Genres))
+ {
+ var songs = item.RecursiveChildren.OfType<Audio>().ToList();
- var currentList = item.Genres.ToList();
+ var currentList = item.Genres.ToList();
- item.Genres = songs.SelectMany(i => i.Genres)
- .Distinct(StringComparer.OrdinalIgnoreCase)
- .ToList();
+ item.Genres = songs.SelectMany(i => i.Genres)
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToList();
- if (currentList.Count != item.Genres.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
- {
- updateType = updateType | ItemUpdateType.MetadataDownload;
+ if (currentList.Count != item.Genres.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
+ {
+ updateType = updateType | ItemUpdateType.MetadataEdit;
+ }
}
}
diff --git a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
index 94d682f44..123ff9e29 100644
--- a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
+++ b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
@@ -83,6 +83,10 @@ namespace MediaBrowser.Providers.Music
{
}
+ catch (DirectoryNotFoundException)
+ {
+
+ }
}
var language = item.GetPreferredMetadataLanguage();
diff --git a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs
index a8df95fd1..6f633cfc8 100644
--- a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs
+++ b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs
@@ -91,6 +91,10 @@ namespace MediaBrowser.Providers.Music
{
}
+ catch (DirectoryNotFoundException)
+ {
+
+ }
}
var language = item.GetPreferredMetadataLanguage();
diff --git a/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs b/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs
index 63d054664..253acc13f 100644
--- a/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs
@@ -83,6 +83,10 @@ namespace MediaBrowser.Providers.People
{
return null;
}
+ catch (DirectoryNotFoundException)
+ {
+ return null;
+ }
}
private RemoteImageInfo GetImageInfo(string xmlFile, string personName, CancellationToken cancellationToken)
diff --git a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs
index 05244af74..9f0cd4ff1 100644
--- a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs
+++ b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs
@@ -98,6 +98,10 @@ namespace MediaBrowser.Providers.TV
{
// No biggie. Don't blow up
}
+ catch (DirectoryNotFoundException)
+ {
+ // No biggie. Don't blow up
+ }
}
}
diff --git a/MediaBrowser.Providers/TV/FanartSeriesProvider.cs b/MediaBrowser.Providers/TV/FanartSeriesProvider.cs
index afc71698b..8ba25e9f1 100644
--- a/MediaBrowser.Providers/TV/FanartSeriesProvider.cs
+++ b/MediaBrowser.Providers/TV/FanartSeriesProvider.cs
@@ -106,6 +106,10 @@ namespace MediaBrowser.Providers.TV
{
// No biggie. Don't blow up
}
+ catch (DirectoryNotFoundException)
+ {
+ // No biggie. Don't blow up
+ }
}
var language = item.GetPreferredMetadataLanguage();
diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
index 21d41ca00..0b52956de 100644
--- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
@@ -2,6 +2,7 @@
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
@@ -22,14 +23,16 @@ namespace MediaBrowser.Providers.TV
private readonly IServerConfigurationManager _config;
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
+ private readonly ILocalizationManager _localization;
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
- public MissingEpisodeProvider(ILogger logger, IServerConfigurationManager config, ILibraryManager libraryManager)
+ public MissingEpisodeProvider(ILogger logger, IServerConfigurationManager config, ILibraryManager libraryManager, ILocalizationManager localization)
{
_logger = logger;
_config = config;
_libraryManager = libraryManager;
+ _localization = localization;
}
public async Task Run(IEnumerable<IGrouping<string, Series>> series, CancellationToken cancellationToken)
@@ -93,16 +96,16 @@ namespace MediaBrowser.Providers.TV
var hasBadData = HasInvalidContent(group);
- var anySeasonsRemoved = await RemoveObsoleteOrMissingSeasons(group, episodeLookup, false)
+ var anySeasonsRemoved = await RemoveObsoleteOrMissingSeasons(group, episodeLookup)
.ConfigureAwait(false);
- var anyEpisodesRemoved = await RemoveObsoleteOrMissingEpisodes(group, episodeLookup, false)
+ var anyEpisodesRemoved = await RemoveObsoleteOrMissingEpisodes(group, episodeLookup)
.ConfigureAwait(false);
var hasNewEpisodes = false;
var hasNewSeasons = false;
- foreach (var series in group.Where(s => s.ContainsEpisodesWithoutSeasonFolders))
+ foreach (var series in group)
{
hasNewSeasons = await AddDummySeasonFolders(series, cancellationToken).ConfigureAwait(false);
}
@@ -165,14 +168,15 @@ namespace MediaBrowser.Providers.TV
/// <returns></returns>
private async Task<bool> AddDummySeasonFolders(Series series, CancellationToken cancellationToken)
{
- var existingEpisodes = series.RecursiveChildren
+ var episodesInSeriesFolder = series.RecursiveChildren
.OfType<Episode>()
+ .Where(i => !i.IsInSeasonFolder)
.ToList();
var hasChanges = false;
// Loop through the unique season numbers
- foreach (var seasonNumber in existingEpisodes.Select(i => i.ParentIndexNumber ?? -1)
+ foreach (var seasonNumber in episodesInSeriesFolder.Select(i => i.ParentIndexNumber ?? -1)
.Where(i => i >= 0)
.Distinct()
.ToList())
@@ -188,6 +192,20 @@ namespace MediaBrowser.Providers.TV
}
}
+ // Unknown season - create a dummy season to put these under
+ if (episodesInSeriesFolder.Any(i => !i.ParentIndexNumber.HasValue))
+ {
+ var hasSeason = series.Children.OfType<Season>()
+ .Any(i => !i.IndexNumber.HasValue);
+
+ if (!hasSeason)
+ {
+ await AddSeason(series, null, cancellationToken).ConfigureAwait(false);
+
+ hasChanges = true;
+ }
+ }
+
return hasChanges;
}
@@ -292,8 +310,7 @@ namespace MediaBrowser.Providers.TV
/// Removes the virtual entry after a corresponding physical version has been added
/// </summary>
private async Task<bool> RemoveObsoleteOrMissingEpisodes(IEnumerable<Series> series,
- IEnumerable<Tuple<int, int>> episodeLookup,
- bool forceRemoveAll)
+ IEnumerable<Tuple<int, int>> episodeLookup)
{
var existingEpisodes = (from s in series
let seasonOffset = TvdbSeriesProvider.GetSeriesOffset(s.ProviderIds) ?? ((s.AnimeSeriesIndex ?? 1) - 1)
@@ -312,11 +329,6 @@ namespace MediaBrowser.Providers.TV
var episodesToRemove = virtualEpisodes
.Where(i =>
{
- if (forceRemoveAll)
- {
- return true;
- }
-
if (i.Episode.IndexNumber.HasValue && i.Episode.ParentIndexNumber.HasValue)
{
var seasonNumber = i.Episode.ParentIndexNumber.Value + i.SeasonOffset;
@@ -362,11 +374,9 @@ namespace MediaBrowser.Providers.TV
/// </summary>
/// <param name="series">The series.</param>
/// <param name="episodeLookup">The episode lookup.</param>
- /// <param name="forceRemoveAll">if set to <c>true</c> [force remove all].</param>
/// <returns>Task{System.Boolean}.</returns>
private async Task<bool> RemoveObsoleteOrMissingSeasons(IEnumerable<Series> series,
- IEnumerable<Tuple<int, int>> episodeLookup,
- bool forceRemoveAll)
+ IEnumerable<Tuple<int, int>> episodeLookup)
{
var existingSeasons = (from s in series
let seasonOffset = TvdbSeriesProvider.GetSeriesOffset(s.ProviderIds) ?? ((s.AnimeSeriesIndex ?? 1) - 1)
@@ -385,11 +395,6 @@ namespace MediaBrowser.Providers.TV
var seasonsToRemove = virtualSeasons
.Where(i =>
{
- if (forceRemoveAll)
- {
- return true;
- }
-
if (i.Season.IndexNumber.HasValue)
{
var seasonNumber = i.Season.IndexNumber.Value + i.SeasonOffset;
@@ -409,7 +414,9 @@ namespace MediaBrowser.Providers.TV
return false;
}
- return true;
+ // Season does not have a number
+ // Remove if there are no episodes directly in series without a season number
+ return i.Season.Series.RecursiveChildren.OfType<Episode>().All(s => s.ParentIndexNumber.HasValue || s.IsInSeasonFolder);
})
.ToList();
@@ -472,20 +479,22 @@ namespace MediaBrowser.Providers.TV
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{Season}.</returns>
private async Task<Season> AddSeason(Series series,
- int seasonNumber,
+ int? seasonNumber,
CancellationToken cancellationToken)
{
- _logger.Info("Creating Season {0} entry for {1}", seasonNumber, series.Name);
+ var seasonName = seasonNumber == 0 ?
+ _config.Configuration.SeasonZeroDisplayName :
+ (seasonNumber.HasValue ? string.Format(_localization.GetLocalizedString("NameSeasonNumber"), seasonNumber.Value.ToString(UsCulture)) : _localization.GetLocalizedString("NameSeasonUnknown"));
- var name = seasonNumber == 0 ? _config.Configuration.SeasonZeroDisplayName : string.Format("Season {0}", seasonNumber.ToString(UsCulture));
+ _logger.Info("Creating Season {0} entry for {1}", seasonName, series.Name);
var season = new Season
{
- Name = name,
+ Name = seasonName,
IndexNumber = seasonNumber,
Parent = series,
DisplayMediaType = typeof(Season).Name,
- Id = (series.Id + seasonNumber.ToString(UsCulture) + name).GetMBId(typeof(Season))
+ Id = (series.Id + (seasonNumber ?? -1).ToString(UsCulture) + seasonName).GetMBId(typeof(Season))
};
await series.AddChild(season, cancellationToken).ConfigureAwait(false);
diff --git a/MediaBrowser.Providers/TV/MovieDbSeasonProvider.cs b/MediaBrowser.Providers/TV/MovieDbSeasonProvider.cs
index 26b2711de..80f4c9485 100644
--- a/MediaBrowser.Providers/TV/MovieDbSeasonProvider.cs
+++ b/MediaBrowser.Providers/TV/MovieDbSeasonProvider.cs
@@ -52,18 +52,18 @@ namespace MediaBrowser.Providers.TV
if (!string.IsNullOrWhiteSpace(seriesTmdbId) && seasonNumber.HasValue)
{
- result.HasMetadata = true;
- result.Item = new Season();
-
try
{
var seasonInfo = await GetSeasonInfo(seriesTmdbId, seasonNumber.Value, info.MetadataLanguage, cancellationToken)
.ConfigureAwait(false);
+ result.HasMetadata = true;
+ result.Item = new Season();
result.Item.Name = info.Name;
- result.Item.Overview = seasonInfo.overview;
result.Item.IndexNumber = seasonNumber;
+ result.Item.Overview = seasonInfo.overview;
+
if (seasonInfo.external_ids.tvdb_id > 0)
{
result.Item.SetProviderId(MetadataProviders.Tvdb, seasonInfo.external_ids.tvdb_id.ToString(CultureInfo.InvariantCulture));
diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
index a7571f7ad..713d46347 100644
--- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs
+++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
@@ -7,7 +7,6 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Providers.Manager;
using System.Collections.Generic;
-using System.Linq;
namespace MediaBrowser.Providers.TV
{
@@ -54,29 +53,5 @@ namespace MediaBrowser.Providers.TV
target.DisplaySpecialsWithSeasons = source.DisplaySpecialsWithSeasons;
}
}
-
- protected override ItemUpdateType BeforeSave(Series item)
- {
- var updateType = base.BeforeSave(item);
-
- var episodes = item.RecursiveChildren
- .OfType<Episode>()
- .ToList();
-
- var dateLastEpisodeAdded = item.DateLastEpisodeAdded;
-
- item.DateLastEpisodeAdded = episodes
- .Where(i => i.LocationType != LocationType.Virtual)
- .Select(i => i.DateCreated)
- .OrderByDescending(i => i)
- .FirstOrDefault();
-
- if (dateLastEpisodeAdded != item.DateLastEpisodeAdded)
- {
- updateType = updateType | ItemUpdateType.MetadataImport;
- }
-
- return updateType;
- }
}
}
diff --git a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs
index d350d2fe4..17a219f52 100644
--- a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs
+++ b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs
@@ -1,11 +1,12 @@
-using System.Collections.Generic;
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -25,12 +26,14 @@ namespace MediaBrowser.Providers.TV
private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _config;
private readonly ILogger _logger;
+ private readonly ILocalizationManager _localization;
- public SeriesPostScanTask(ILibraryManager libraryManager, ILogger logger, IServerConfigurationManager config)
+ public SeriesPostScanTask(ILibraryManager libraryManager, ILogger logger, IServerConfigurationManager config, ILocalizationManager localization)
{
_libraryManager = libraryManager;
_logger = logger;
_config = config;
+ _localization = localization;
}
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
@@ -47,7 +50,7 @@ namespace MediaBrowser.Providers.TV
var seriesGroups = FindSeriesGroups(seriesList).Where(g => !string.IsNullOrEmpty(g.Key)).ToList();
- await new MissingEpisodeProvider(_logger, _config, _libraryManager).Run(seriesGroups, cancellationToken).ConfigureAwait(false);
+ await new MissingEpisodeProvider(_logger, _config, _libraryManager, _localization).Run(seriesGroups, cancellationToken).ConfigureAwait(false);
var numComplete = 0;
@@ -73,10 +76,6 @@ namespace MediaBrowser.Providers.TV
.Select(i => i.Id)
.ToList();
- series.DateLastEpisodeAdded = physicalEpisodes.Select(i => i.DateCreated)
- .OrderByDescending(i => i)
- .FirstOrDefault();
-
numComplete++;
double percent = numComplete;
percent /= seriesList.Count;
diff --git a/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs
index ef9f5427c..52c1ab7dd 100644
--- a/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs
@@ -72,6 +72,10 @@ namespace MediaBrowser.Providers.TV
{
// Don't fail the provider because this will just keep on going and going.
}
+ catch (DirectoryNotFoundException)
+ {
+ // Don't fail the provider because this will just keep on going and going.
+ }
}
return list;
@@ -101,6 +105,10 @@ namespace MediaBrowser.Providers.TV
{
// Don't fail the provider because this will just keep on going and going.
}
+ catch (DirectoryNotFoundException)
+ {
+ // Don't fail the provider because this will just keep on going and going.
+ }
}
return result;
@@ -208,8 +216,9 @@ namespace MediaBrowser.Providers.TV
/// Fetches the episode data.
/// </summary>
/// <param name="id">The identifier.</param>
+ /// <param name="identity">The identity.</param>
/// <param name="seriesDataPath">The series data path.</param>
- /// <param name="seriesProviderIds"></param>
+ /// <param name="seriesProviderIds">The series provider ids.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{System.Boolean}.</returns>
private Episode FetchEpisodeData(EpisodeInfo id, EpisodeIdentity identity, string seriesDataPath, Dictionary<string, string> seriesProviderIds, CancellationToken cancellationToken)
@@ -279,6 +288,10 @@ namespace MediaBrowser.Providers.TV
{
break;
}
+ catch (DirectoryNotFoundException)
+ {
+ break;
+ }
episodeNumber++;
}
diff --git a/MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs
index efafeae96..1ebd7bed5 100644
--- a/MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs
@@ -94,6 +94,10 @@ namespace MediaBrowser.Providers.TV
{
// No tvdb data yet. Don't blow up
}
+ catch (DirectoryNotFoundException)
+ {
+ // No tvdb data yet. Don't blow up
+ }
}
return new RemoteImageInfo[] { };
diff --git a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs
index 9cc09c40c..08913d3b4 100644
--- a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs
@@ -87,6 +87,10 @@ namespace MediaBrowser.Providers.TV
{
// No tvdb data yet. Don't blow up
}
+ catch (DirectoryNotFoundException)
+ {
+ // No tvdb data yet. Don't blow up
+ }
}
return new RemoteImageInfo[] { };
diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs
index bfdbb8ccf..ce939aeb4 100644
--- a/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs
+++ b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs
@@ -95,7 +95,7 @@ namespace MediaBrowser.Server.Implementations.Channels
public static string GetUserDistinctValue(User user)
{
- var channels = user.Configuration.BlockedChannels
+ var channels = user.Policy.BlockedChannels
.OrderBy(i => i)
.ToList();
diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
index 9e7679f93..c12a1161b 100644
--- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
+++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
@@ -1,6 +1,4 @@
-using System.Net;
-using System.Text;
-using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels;
@@ -10,7 +8,6 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Channels;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@@ -24,9 +21,10 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Net;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Server.Implementations.Library;
namespace MediaBrowser.Server.Implementations.Channels
{
@@ -179,12 +177,9 @@ namespace MediaBrowser.Server.Implementations.Channels
var internalResult = await GetChannelsInternal(query, cancellationToken).ConfigureAwait(false);
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields))
- .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
- .ToList();
+ var dtoOptions = new DtoOptions();
- var returnItems = internalResult.Items.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
+ var returnItems = internalResult.Items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user))
.ToArray();
var result = new QueryResult<BaseItemDto>
@@ -544,11 +539,6 @@ namespace MediaBrowser.Server.Implementations.Channels
var internalResult = await GetLatestChannelItemsInternal(query, cancellationToken).ConfigureAwait(false);
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields))
- .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
- .ToList();
-
var items = internalResult.Items;
var totalRecordCount = internalResult.TotalRecordCount;
@@ -563,7 +553,9 @@ namespace MediaBrowser.Server.Implementations.Channels
totalRecordCount = items.Length;
}
- var returnItems = items.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
+ var dtoOptions = new DtoOptions();
+
+ var returnItems = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user))
.ToArray();
var result = new QueryResult<BaseItemDto>
@@ -828,12 +820,9 @@ namespace MediaBrowser.Server.Implementations.Channels
await RefreshIfNeeded(internalResult.Items, new Progress<double>(), cancellationToken).ConfigureAwait(false);
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields))
- .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
- .ToList();
+ var dtoOptions = new DtoOptions();
- var returnItems = internalResult.Items.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
+ var returnItems = internalResult.Items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user))
.ToArray();
var result = new QueryResult<BaseItemDto>
@@ -980,12 +969,9 @@ namespace MediaBrowser.Server.Implementations.Channels
var internalResult = await GetChannelItemsInternal(query, new Progress<double>(), cancellationToken).ConfigureAwait(false);
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields))
- .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
- .ToList();
+ var dtoOptions = new DtoOptions();
- var returnItems = internalResult.Items.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
+ var returnItems = internalResult.Items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user))
.ToArray();
var result = new QueryResult<BaseItemDto>
@@ -1305,16 +1291,16 @@ namespace MediaBrowser.Server.Implementations.Channels
}
}
- private async Task RefreshIfNeeded(BaseItem program, CancellationToken cancellationToken)
+ private readonly Task _cachedTask = Task.FromResult(true);
+ private Task RefreshIfNeeded(BaseItem program, CancellationToken cancellationToken)
{
- if (_refreshedItems.ContainsKey(program.Id))
+ if (!_refreshedItems.ContainsKey(program.Id))
{
- return;
+ _refreshedItems.TryAdd(program.Id, true);
+ return program.RefreshMetadata(cancellationToken);
}
- await program.RefreshMetadata(cancellationToken).ConfigureAwait(false);
-
- _refreshedItems.TryAdd(program.Id, true);
+ return _cachedTask;
}
internal IChannel GetChannelProvider(Channel channel)
@@ -1407,12 +1393,9 @@ namespace MediaBrowser.Server.Implementations.Channels
{
var user = string.IsNullOrEmpty(userId) ? null : _userManager.GetUserById(userId);
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
-
var folder = await GetInternalChannelFolder(userId, cancellationToken).ConfigureAwait(false);
- return _dtoService.GetBaseItemDto(folder, fields, user);
+ return _dtoService.GetBaseItemDto(folder, new DtoOptions(), user);
}
public async Task<Folder> GetInternalChannelFolder(string userId, CancellationToken cancellationToken)
diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelPostScanTask.cs b/MediaBrowser.Server.Implementations/Channels/ChannelPostScanTask.cs
index 72c524ec5..d266cca6c 100644
--- a/MediaBrowser.Server.Implementations/Channels/ChannelPostScanTask.cs
+++ b/MediaBrowser.Server.Implementations/Channels/ChannelPostScanTask.cs
@@ -72,26 +72,29 @@ namespace MediaBrowser.Server.Implementations.Channels
var features = _channelManager.GetChannelFeatures(channelId);
const int currentRefreshLevel = 1;
- var maxRefreshLevel = features.AutoRefreshLevels ?? 1;
+ var maxRefreshLevel = features.AutoRefreshLevels ?? 0;
- var innerProgress = new ActionableProgress<double>();
-
- var startingNumberComplete = numComplete;
- innerProgress.RegisterAction(p =>
+ if (maxRefreshLevel > 0)
{
- double innerPercent = startingNumberComplete;
- innerPercent += (p / 100);
- innerPercent /= numItems;
- progress.Report(innerPercent * 100);
- });
+ var innerProgress = new ActionableProgress<double>();
- try
- {
- await GetAllItems(user, channelId, null, currentRefreshLevel, maxRefreshLevel, innerProgress, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error getting channel content", ex);
+ var startingNumberComplete = numComplete;
+ innerProgress.RegisterAction(p =>
+ {
+ double innerPercent = startingNumberComplete;
+ innerPercent += (p / 100);
+ innerPercent /= numItems;
+ progress.Report(innerPercent * 100);
+ });
+
+ try
+ {
+ await GetAllItems(user, channelId, null, currentRefreshLevel, maxRefreshLevel, innerProgress, cancellationToken).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error getting channel content", ex);
+ }
}
numComplete++;
diff --git a/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs b/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs
index b9896e9ce..b3b8ccbd8 100644
--- a/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs
+++ b/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Configuration;
+using System.Collections.Generic;
+using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Events;
using MediaBrowser.Common.Implementations.Configuration;
using MediaBrowser.Controller;
@@ -32,7 +33,6 @@ namespace MediaBrowser.Server.Implementations.Configuration
: base(applicationPaths, logManager, xmlSerializer)
{
UpdateItemsByNamePath();
- UpdateTranscodingTempPath();
UpdateMetadataPath();
}
@@ -71,12 +71,18 @@ namespace MediaBrowser.Server.Implementations.Configuration
protected override void OnConfigurationUpdated()
{
UpdateItemsByNamePath();
- UpdateTranscodingTempPath();
UpdateMetadataPath();
base.OnConfigurationUpdated();
}
+ public override void AddParts(IEnumerable<IConfigurationFactory> factories)
+ {
+ base.AddParts(factories);
+
+ UpdateTranscodingTempPath();
+ }
+
/// <summary>
/// Updates the items by name path.
/// </summary>
@@ -102,9 +108,21 @@ namespace MediaBrowser.Server.Implementations.Configuration
/// </summary>
private void UpdateTranscodingTempPath()
{
- ((ServerApplicationPaths)ApplicationPaths).TranscodingTempPath = string.IsNullOrEmpty(Configuration.TranscodingTempPath) ?
+ var encodingConfig = this.GetConfiguration<EncodingOptions>("encoding");
+
+ ((ServerApplicationPaths)ApplicationPaths).TranscodingTempPath = string.IsNullOrEmpty(encodingConfig.TranscodingTempPath) ?
null :
- Configuration.TranscodingTempPath;
+ encodingConfig.TranscodingTempPath;
+ }
+
+ protected override void OnNamedConfigurationUpdated(string key, object configuration)
+ {
+ base.OnNamedConfigurationUpdated(key, configuration);
+
+ if (string.Equals(key, "encoding", StringComparison.OrdinalIgnoreCase))
+ {
+ UpdateTranscodingTempPath();
+ }
}
/// <summary>
@@ -117,7 +135,6 @@ namespace MediaBrowser.Server.Implementations.Configuration
var newConfig = (ServerConfiguration)newConfiguration;
ValidateItemByNamePath(newConfig);
- ValidateTranscodingTempPath(newConfig);
ValidatePathSubstitutions(newConfig);
ValidateMetadataPath(newConfig);
@@ -158,26 +175,6 @@ namespace MediaBrowser.Server.Implementations.Configuration
}
/// <summary>
- /// Validates the transcoding temporary path.
- /// </summary>
- /// <param name="newConfig">The new configuration.</param>
- /// <exception cref="DirectoryNotFoundException"></exception>
- private void ValidateTranscodingTempPath(ServerConfiguration newConfig)
- {
- var newPath = newConfig.TranscodingTempPath;
-
- if (!string.IsNullOrWhiteSpace(newPath)
- && !string.Equals(Configuration.TranscodingTempPath ?? string.Empty, newPath))
- {
- // Validate
- if (!Directory.Exists(newPath))
- {
- throw new DirectoryNotFoundException(string.Format("{0} does not exist.", newPath));
- }
- }
- }
-
- /// <summary>
/// Validates the metadata path.
/// </summary>
/// <param name="newConfig">The new configuration.</param>
diff --git a/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs b/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs
index 52c252d24..52ec5c9b1 100644
--- a/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs
+++ b/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs
@@ -34,7 +34,7 @@ namespace MediaBrowser.Server.Implementations.Connect
{
LoadCachedAddress();
- _timer = new Timer(TimerCallback, null, TimeSpan.FromSeconds(10), TimeSpan.FromHours(6));
+ _timer = new Timer(TimerCallback, null, TimeSpan.FromSeconds(5), TimeSpan.FromHours(3));
}
private async void TimerCallback(object state)
diff --git a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
index cbd75cdeb..67d844543 100644
--- a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
+++ b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
@@ -1,4 +1,5 @@
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
@@ -432,9 +433,7 @@ namespace MediaBrowser.Server.Implementations.Connect
await user.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
- user.Configuration.SyncConnectImage = false;
- user.Configuration.SyncConnectName = false;
- _userManager.UpdateConfiguration(user, user.Configuration);
+ await _userManager.UpdateConfiguration(user.Id.ToString("N"), user.Configuration);
await RefreshAuthorizationsInternal(false, CancellationToken.None).ConfigureAwait(false);
@@ -791,7 +790,7 @@ namespace MediaBrowser.Server.Implementations.Connect
if (user == null)
{
// Add user
- user = await _userManager.CreateUser(connectEntry.UserName).ConfigureAwait(false);
+ user = await _userManager.CreateUser(_userManager.MakeValidUsername(connectEntry.UserName)).ConfigureAwait(false);
user.ConnectUserName = connectEntry.UserName;
user.ConnectUserId = connectEntry.UserId;
@@ -800,23 +799,21 @@ namespace MediaBrowser.Server.Implementations.Connect
await _userManager.UpdateUser(user).ConfigureAwait(false);
- user.Configuration.SyncConnectImage = true;
- user.Configuration.SyncConnectName = true;
- user.Configuration.IsHidden = true;
- user.Configuration.EnableLiveTvManagement = false;
- user.Configuration.EnableContentDeletion = false;
- user.Configuration.EnableRemoteControlOfOtherUsers = false;
- user.Configuration.EnableSharedDeviceControl = false;
- user.Configuration.IsAdministrator = false;
+ user.Policy.IsHidden = true;
+ user.Policy.EnableLiveTvManagement = false;
+ user.Policy.EnableContentDeletion = false;
+ user.Policy.EnableRemoteControlOfOtherUsers = false;
+ user.Policy.EnableSharedDeviceControl = false;
+ user.Policy.IsAdministrator = false;
if (currentPendingEntry != null)
{
- user.Configuration.EnableLiveTvAccess = currentPendingEntry.EnableLiveTv;
- user.Configuration.BlockedMediaFolders = currentPendingEntry.ExcludedLibraries;
- user.Configuration.BlockedChannels = currentPendingEntry.ExcludedChannels;
+ user.Policy.EnableLiveTvAccess = currentPendingEntry.EnableLiveTv;
+ user.Policy.BlockedMediaFolders = currentPendingEntry.ExcludedLibraries;
+ user.Policy.BlockedChannels = currentPendingEntry.ExcludedChannels;
}
- _userManager.UpdateConfiguration(user, user.Configuration);
+ await _userManager.UpdateConfiguration(user.Id.ToString("N"), user.Configuration);
}
}
else if (string.Equals(connectEntry.AcceptStatus, "waiting", StringComparison.OrdinalIgnoreCase))
@@ -844,7 +841,7 @@ namespace MediaBrowser.Server.Implementations.Connect
{
var users = _userManager.Users
.Where(i => !string.IsNullOrEmpty(i.ConnectUserId) &&
- (i.Configuration.SyncConnectImage || i.Configuration.SyncConnectName))
+ (i.ConnectLinkType.HasValue && i.ConnectLinkType.Value == UserLinkType.Guest))
.ToList();
foreach (var user in users)
@@ -857,7 +854,10 @@ namespace MediaBrowser.Server.Implementations.Connect
continue;
}
- if (user.Configuration.SyncConnectName)
+ var syncConnectName = true;
+ var syncConnectImage = true;
+
+ if (syncConnectName)
{
var changed = !string.Equals(authorization.UserName, user.Name, StringComparison.OrdinalIgnoreCase);
@@ -867,7 +867,7 @@ namespace MediaBrowser.Server.Implementations.Connect
}
}
- if (user.Configuration.SyncConnectImage)
+ if (syncConnectImage)
{
var imageUrl = authorization.UserImageUrl;
diff --git a/MediaBrowser.Server.Implementations/Devices/DeviceManager.cs b/MediaBrowser.Server.Implementations/Devices/DeviceManager.cs
index 6cdc58118..ddd5ef58d 100644
--- a/MediaBrowser.Server.Implementations/Devices/DeviceManager.cs
+++ b/MediaBrowser.Server.Implementations/Devices/DeviceManager.cs
@@ -5,9 +5,11 @@ using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Devices;
using MediaBrowser.Model.Events;
+using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Session;
+using MediaBrowser.Model.Users;
using System;
using System.Collections.Generic;
using System.IO;
@@ -103,7 +105,12 @@ namespace MediaBrowser.Server.Implementations.Devices
var val = query.SupportsUniqueIdentifier.Value;
devices = devices.Where(i => GetCapabilities(i.Id).SupportsUniqueIdentifier == val);
- }
+ }
+
+ if (!string.IsNullOrWhiteSpace(query.UserId))
+ {
+ devices = devices.Where(i => CanAccessDevice(query.UserId, i.Id));
+ }
var array = devices.ToArray();
return new QueryResult<DeviceInfo>
@@ -188,6 +195,41 @@ namespace MediaBrowser.Server.Implementations.Devices
EventHelper.FireEventIfNotNull(DeviceOptionsUpdated, this, new GenericEventArgs<DeviceInfo>(device), _logger);
}
+
+ public bool CanAccessDevice(string userId, string deviceId)
+ {
+ if (string.IsNullOrWhiteSpace(userId))
+ {
+ throw new ArgumentNullException("userId");
+ }
+ if (string.IsNullOrWhiteSpace(deviceId))
+ {
+ throw new ArgumentNullException("deviceId");
+ }
+
+ var user = _userManager.GetUserById(userId);
+ if (!CanAccessDevice(user.Policy, deviceId))
+ {
+ var capabilities = GetCapabilities(deviceId);
+
+ if (capabilities.SupportsUniqueIdentifier)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private bool CanAccessDevice(UserPolicy policy, string id)
+ {
+ if (policy.EnableAllDevices)
+ {
+ return true;
+ }
+
+ return ListHelper.ContainsIgnoreCase(policy.EnabledDevices, id);
+ }
}
public class DevicesConfigStore : IConfigurationFactory
diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
index b141fea1e..5055d2750 100644
--- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
+++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
@@ -78,6 +78,11 @@ namespace MediaBrowser.Server.Implementations.Drawing
// No biggie
sizeDictionary = new Dictionary<Guid, ImageSize>();
}
+ catch (DirectoryNotFoundException)
+ {
+ // No biggie
+ sizeDictionary = new Dictionary<Guid, ImageSize>();
+ }
catch (Exception ex)
{
logger.ErrorException("Error parsing image size cache file", ex);
@@ -362,7 +367,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
new UnplayedCountIndicator().DrawUnplayedCountIndicator(graphics, currentImageSize, options.UnplayedCount.Value);
}
- if (options.PercentPlayed >= 0)
+ if (options.PercentPlayed > 0)
{
var currentImageSize = new Size(imageWidth, imageHeight);
diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
index 1020a4373..362ee80b0 100644
--- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs
+++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
@@ -73,11 +73,6 @@ namespace MediaBrowser.Server.Implementations.Dto
{
Fields = fields
};
-
- // Get everything
- options.ImageTypes = Enum.GetNames(typeof(ImageType))
- .Select(i => (ImageType)Enum.Parse(typeof(ImageType), i, true))
- .ToList();
return GetBaseItemDto(item, options, user, owner);
}
@@ -121,8 +116,6 @@ namespace MediaBrowser.Server.Implementations.Dto
ServerId = _appHost.SystemId
};
- dto.SupportsPlaylists = item.SupportsAddingToPlaylist;
-
if (fields.Contains(ItemFields.People))
{
AttachPeople(dto, item);
@@ -275,6 +268,21 @@ namespace MediaBrowser.Server.Implementations.Dto
}
dto.PlayAccess = item.GetPlayAccess(user);
+
+ if (fields.Contains(ItemFields.SeasonUserData))
+ {
+ var episode = item as Episode;
+
+ if (episode != null)
+ {
+ var season = episode.Season;
+
+ if (season != null)
+ {
+ dto.SeasonUserData = _userDataRepository.GetUserDataDto(season, user);
+ }
+ }
+ }
}
private int GetChildCount(Folder folder, User user)
@@ -750,7 +758,7 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.AspectRatio = hasAspectRatio.AspectRatio;
}
- if (fields.Contains(ItemFields.ProductionLocations))
+ if (fields.Contains(ItemFields.Metascore))
{
var hasMetascore = item as IHasMetascore;
if (hasMetascore != null)
@@ -1132,15 +1140,22 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.AbsoluteEpisodeNumber = episode.AbsoluteEpisodeNumber;
}
- dto.AirsAfterSeasonNumber = episode.AirsAfterSeasonNumber;
- dto.AirsBeforeEpisodeNumber = episode.AirsBeforeEpisodeNumber;
- dto.AirsBeforeSeasonNumber = episode.AirsBeforeSeasonNumber;
+ //if (fields.Contains(ItemFields.SpecialEpisodeNumbers))
+ {
+ dto.AirsAfterSeasonNumber = episode.AirsAfterSeasonNumber;
+ dto.AirsBeforeEpisodeNumber = episode.AirsBeforeEpisodeNumber;
+ dto.AirsBeforeSeasonNumber = episode.AirsBeforeSeasonNumber;
+ }
var episodeSeason = episode.Season;
if (episodeSeason != null)
{
dto.SeasonId = episodeSeason.Id.ToString("N");
- dto.SeasonName = episodeSeason.Name;
+
+ if (fields.Contains(ItemFields.SeasonName))
+ {
+ dto.SeasonName = episodeSeason.Name;
+ }
}
if (fields.Contains(ItemFields.SeriesGenres))
@@ -1180,7 +1195,11 @@ namespace MediaBrowser.Server.Implementations.Dto
{
dto.SeriesId = GetDtoId(series);
dto.SeriesName = series.Name;
- dto.AirTime = series.AirTime;
+
+ if (fields.Contains(ItemFields.AirTime))
+ {
+ dto.AirTime = series.AirTime;
+ }
if (options.GetImageLimit(ImageType.Thumb) > 0)
{
diff --git a/MediaBrowser.Server.Implementations/EntryPoints/UsageEntryPoint.cs b/MediaBrowser.Server.Implementations/EntryPoints/UsageEntryPoint.cs
index de53201c9..0e99ee673 100644
--- a/MediaBrowser.Server.Implementations/EntryPoints/UsageEntryPoint.cs
+++ b/MediaBrowser.Server.Implementations/EntryPoints/UsageEntryPoint.cs
@@ -1,7 +1,7 @@
using MediaBrowser.Common;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Implementations.Security;
using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Logging;
@@ -22,6 +22,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
private readonly IHttpClient _httpClient;
private readonly ILogger _logger;
private readonly ISessionManager _sessionManager;
+ private readonly IUserManager _userManager;
private Timer _timer;
private readonly TimeSpan _frequency = TimeSpan.FromHours(24);
@@ -66,7 +67,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
{
try
{
- await new UsageReporter(_applicationHost, _networkManager, _httpClient)
+ await new UsageReporter(_applicationHost, _networkManager, _httpClient, _userManager)
.ReportAppUsage(client, CancellationToken.None)
.ConfigureAwait(false);
}
@@ -108,7 +109,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
{
try
{
- await new UsageReporter(_applicationHost, _networkManager, _httpClient)
+ await new UsageReporter(_applicationHost, _networkManager, _httpClient, _userManager)
.ReportServerUsage(CancellationToken.None)
.ConfigureAwait(false);
}
diff --git a/MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs b/MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs
index 36ba55828..5be267313 100644
--- a/MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs
+++ b/MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs
@@ -1,7 +1,11 @@
using MediaBrowser.Common;
using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Connect;
using System;
using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -12,13 +16,15 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
private readonly IApplicationHost _applicationHost;
private readonly INetworkManager _networkManager;
private readonly IHttpClient _httpClient;
+ private readonly IUserManager _userManager;
private const string MbAdminUrl = "http://www.mb3admin.com/admin/";
- public UsageReporter(IApplicationHost applicationHost, INetworkManager networkManager, IHttpClient httpClient)
+ public UsageReporter(IApplicationHost applicationHost, INetworkManager networkManager, IHttpClient httpClient, IUserManager userManager)
{
_applicationHost = applicationHost;
_networkManager = networkManager;
_httpClient = httpClient;
+ _userManager = userManager;
}
public Task ReportServerUsage(CancellationToken cancellationToken)
@@ -38,6 +44,12 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
{ "isservice", _applicationHost.IsRunningAsService.ToString().ToLower()}
};
+ var users = _userManager.Users.ToList();
+
+ data["localusers"] = users.Count(i => !i.ConnectLinkType.HasValue).ToString(CultureInfo.InvariantCulture);
+ data["guests"] = users.Count(i => i.ConnectLinkType.HasValue && i.ConnectLinkType.Value == UserLinkType.Guest).ToString(CultureInfo.InvariantCulture);
+ data["linkedusers"] = users.Count(i => i.ConnectLinkType.HasValue && i.ConnectLinkType.Value == UserLinkType.LinkedUser).ToString(CultureInfo.InvariantCulture);
+
return _httpClient.Post(MbAdminUrl + "service/registration/ping", data, cancellationToken);
}
diff --git a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
index d4625d402..a4ac22eea 100644
--- a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
+++ b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
@@ -4,11 +4,11 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.FileOrganization;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Controller.Resolvers;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.FileOrganization;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Naming.Common;
+using MediaBrowser.Naming.IO;
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -16,8 +16,6 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Server.Implementations.Library;
-using MediaBrowser.Server.Implementations.Library.Resolvers.TV;
namespace MediaBrowser.Server.Implementations.FileOrganization
{
@@ -57,18 +55,23 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
FileSize = new FileInfo(path).Length
};
- var seriesName = SeriesResolver.GetSeriesNameFromEpisodeFile(path);
+ var resolver = new Naming.TV.EpisodeResolver(new ExtendedNamingOptions(), new Naming.Logging.NullLogger());
+
+ var episodeInfo = resolver.Resolve(path, FileInfoType.File) ??
+ new Naming.TV.EpisodeInfo();
+
+ var seriesName = episodeInfo.SeriesName;
if (!string.IsNullOrEmpty(seriesName))
{
- var season = SeriesResolver.GetSeasonNumberFromEpisodeFile(path);
+ var season = episodeInfo.SeasonNumber;
result.ExtractedSeasonNumber = season;
if (season.HasValue)
{
// Passing in true will include a few extra regex's
- var episode = SeriesResolver.GetEpisodeNumberFromFile(path, true);
+ var episode = episodeInfo.EpisodeNumber;
result.ExtractedEpisodeNumber = episode;
@@ -76,7 +79,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
{
_logger.Debug("Extracted information from {0}. Series name {1}, Season {2}, Episode {3}", path, seriesName, season, episode);
- var endingEpisodeNumber = SeriesResolver.GetEndingEpisodeNumberFromFile(path);
+ var endingEpisodeNumber = episodeInfo.EndingEpsiodeNumber;
result.ExtractedEndingEpisodeNumber = endingEpisodeNumber;
@@ -251,7 +254,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
var folder = Path.GetDirectoryName(targetPath);
var targetFileNameWithoutExtension = _fileSystem.GetFileNameWithoutExtension(targetPath);
-
+
try
{
var filesOfOtherExtensions = Directory.EnumerateFiles(folder, "*", SearchOption.TopDirectoryOnly)
@@ -370,6 +373,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
if (episode == null)
{
+ _logger.Warn("No provider metadata found for {0} season {1} episode {2}", series.Name, seasonNumber, episodeNumber);
return null;
}
@@ -456,11 +460,11 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
private bool IsSameEpisode(string sourcePath, string newPath)
{
- var sourceFileInfo = new FileInfo(sourcePath);
- var destinationFileInfo = new FileInfo(newPath);
-
try
{
+ var sourceFileInfo = new FileInfo(sourcePath);
+ var destinationFileInfo = new FileInfo(newPath);
+
if (sourceFileInfo.Length == destinationFileInfo.Length)
{
return true;
@@ -470,6 +474,10 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
{
return false;
}
+ catch (DirectoryNotFoundException)
+ {
+ return false;
+ }
return false;
}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
index 56e2e5247..c3228db92 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -1,7 +1,6 @@
using Funq;
using MediaBrowser.Common;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Logging;
using MediaBrowser.Server.Implementations.HttpServer.NetListener;
@@ -205,10 +204,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
{
HostContext.Config.HandlerFactoryPath = ListenerRequest.GetHandlerPathIfAny(UrlPrefixes.First());
- _listener = _supportsNativeWebSocket && NativeWebSocket.IsSupported
- ? _listener = new HttpListenerServer(_logger, OnRequestReceived)
- //? _listener = new WebSocketSharpListener(_logger, OnRequestReceived)
- : _listener = new WebSocketSharpListener(_logger, OnRequestReceived);
+ _listener = GetListener();
_listener.WebSocketHandler = WebSocketHandler;
_listener.ErrorHandler = ErrorHandler;
@@ -217,6 +213,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer
_listener.Start(UrlPrefixes);
}
+ private IHttpListener GetListener()
+ {
+ if (_supportsNativeWebSocket && NativeWebSocket.IsSupported)
+ {
+ return new HttpListenerServer(_logger, OnRequestReceived);
+ }
+
+ return new WebSocketSharpListener(_logger, OnRequestReceived);
+ }
+
private void WebSocketHandler(WebSocketConnectEventArgs args)
{
if (WebSocketConnected != null)
diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
index e13e27d5a..681d3ac5e 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
@@ -12,7 +12,7 @@ using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
-using MimeTypes = MediaBrowser.Common.Net.MimeTypes;
+using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
namespace MediaBrowser.Server.Implementations.HttpServer
{
diff --git a/MediaBrowser.Server.Implementations/HttpServer/IHttpListener.cs b/MediaBrowser.Server.Implementations/HttpServer/IHttpListener.cs
index 86e8856cf..e77600e93 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/IHttpListener.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/IHttpListener.cs
@@ -1,8 +1,8 @@
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Net;
using ServiceStack.Web;
using System;
using System.Collections.Generic;
+using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.HttpServer
{
diff --git a/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs b/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs
index 8cc614fe5..cac2f8e09 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs
@@ -1,9 +1,9 @@
-using System.Text;
-using MediaBrowser.Common.Events;
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.Events;
+using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Logging;
using System;
using System.Net.WebSockets;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WebSocketMessageType = MediaBrowser.Model.Net.WebSocketMessageType;
diff --git a/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs b/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs
index 2d41cc26f..31c0e87b3 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs
@@ -1,4 +1,4 @@
-using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Logging;
using ServiceStack;
using ServiceStack.Host.HttpListener;
diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs
index 57d87749c..1d17c641d 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs
@@ -1,5 +1,6 @@
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Connect;
+using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
@@ -15,10 +16,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
{
private readonly IServerConfigurationManager _config;
- public AuthService(IUserManager userManager, IAuthorizationContext authorizationContext, IServerConfigurationManager config, IConnectManager connectManager, ISessionManager sessionManager)
+ public AuthService(IUserManager userManager, IAuthorizationContext authorizationContext, IServerConfigurationManager config, IConnectManager connectManager, ISessionManager sessionManager, IDeviceManager deviceManager)
{
AuthorizationContext = authorizationContext;
_config = config;
+ DeviceManager = deviceManager;
SessionManager = sessionManager;
ConnectManager = connectManager;
UserManager = userManager;
@@ -28,6 +30,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
public IAuthorizationContext AuthorizationContext { get; private set; }
public IConnectManager ConnectManager { get; private set; }
public ISessionManager SessionManager { get; private set; }
+ public IDeviceManager DeviceManager { get; private set; }
/// <summary>
/// Redirect the client to a specific URL if authentication failed.
@@ -68,24 +71,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
if (user != null)
{
- if (user.Configuration.IsDisabled)
- {
- throw new SecurityException("User account has been disabled.")
- {
- SecurityExceptionType = SecurityExceptionType.Unauthenticated
- };
- }
-
- if (!user.Configuration.IsAdministrator &&
- !authAttribtues.EscapeParentalControl &&
- !user.IsParentalScheduleAllowed())
- {
- request.AddResponseHeader("X-Application-Error-Code", "ParentalControl");
- throw new SecurityException("This user account is not allowed access at this time.")
- {
- SecurityExceptionType = SecurityExceptionType.ParentalControl
- };
- }
+ ValidateUserAccess(user, request, authAttribtues, auth);
}
if (!IsExemptFromRoles(auth, authAttribtues))
@@ -108,6 +94,42 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
}
}
+ private void ValidateUserAccess(User user, IServiceRequest request,
+ IAuthenticationAttributes authAttribtues,
+ AuthorizationInfo auth)
+ {
+ if (user.Policy.IsDisabled)
+ {
+ throw new SecurityException("User account has been disabled.")
+ {
+ SecurityExceptionType = SecurityExceptionType.Unauthenticated
+ };
+ }
+
+ if (!user.Policy.IsAdministrator &&
+ !authAttribtues.EscapeParentalControl &&
+ !user.IsParentalScheduleAllowed())
+ {
+ request.AddResponseHeader("X-Application-Error-Code", "ParentalControl");
+
+ throw new SecurityException("This user account is not allowed access at this time.")
+ {
+ SecurityExceptionType = SecurityExceptionType.ParentalControl
+ };
+ }
+
+ if (!string.IsNullOrWhiteSpace(auth.DeviceId))
+ {
+ if (!DeviceManager.CanAccessDevice(user.Id.ToString("N"), auth.DeviceId))
+ {
+ throw new SecurityException("User is not allowed access from this device.")
+ {
+ SecurityExceptionType = SecurityExceptionType.ParentalControl
+ };
+ }
+ }
+ }
+
private bool IsExemptFromAuthenticationToken(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues)
{
if (!_config.Configuration.IsStartupWizardCompleted &&
@@ -135,7 +157,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
{
if (roles.Contains("admin", StringComparer.OrdinalIgnoreCase))
{
- if (user == null || !user.Configuration.IsAdministrator)
+ if (user == null || !user.Policy.IsAdministrator)
{
throw new SecurityException("User does not have admin access.")
{
@@ -145,7 +167,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
}
if (roles.Contains("delete", StringComparer.OrdinalIgnoreCase))
{
- if (user == null || !user.Configuration.EnableContentDeletion)
+ if (user == null || !user.Policy.EnableContentDeletion)
{
throw new SecurityException("User does not have delete access.")
{
diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/Extensions.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/Extensions.cs
index 63d57b6be..154313fb9 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/Extensions.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/Extensions.cs
@@ -1,6 +1,6 @@
-using System;
-using MediaBrowser.Model.Logging;
-using WebSocketSharp.Net;
+using MediaBrowser.Model.Logging;
+using SocketHttpListener.Net;
+using System;
namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
{
diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs
index 7ff3a1247..9dcb679f4 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs
@@ -1,11 +1,9 @@
-using System.Text;
-using MediaBrowser.Common.Events;
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.Events;
+using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
-using WebSocketMessageType = MediaBrowser.Model.Net.WebSocketMessageType;
using WebSocketState = MediaBrowser.Model.Net.WebSocketState;
namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
@@ -23,7 +21,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
/// Gets or sets the web socket.
/// </summary>
/// <value>The web socket.</value>
- private WebSocketSharp.WebSocket WebSocket { get; set; }
+ private SocketHttpListener.WebSocket WebSocket { get; set; }
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
@@ -33,7 +31,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
/// <param name="socket">The socket.</param>
/// <param name="logger">The logger.</param>
/// <exception cref="System.ArgumentNullException">socket</exception>
- public SharpWebSocket(WebSocketSharp.WebSocket socket, ILogger logger)
+ public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger)
{
if (socket == null)
{
@@ -55,17 +53,17 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
WebSocket.ConnectAsServer();
}
- void socket_OnError(object sender, WebSocketSharp.ErrorEventArgs e)
+ void socket_OnError(object sender, SocketHttpListener.ErrorEventArgs e)
{
EventHelper.FireEventIfNotNull(Closed, this, EventArgs.Empty, _logger);
}
- void socket_OnClose(object sender, WebSocketSharp.CloseEventArgs e)
+ void socket_OnClose(object sender, SocketHttpListener.CloseEventArgs e)
{
EventHelper.FireEventIfNotNull(Closed, this, EventArgs.Empty, _logger);
}
- void socket_OnMessage(object sender, WebSocketSharp.MessageEventArgs e)
+ void socket_OnMessage(object sender, SocketHttpListener.MessageEventArgs e)
{
if (OnReceive != null)
{
diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SocketSharpLogger.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SocketSharpLogger.cs
index ffc99793e..427671b30 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SocketSharpLogger.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SocketSharpLogger.cs
@@ -3,7 +3,7 @@ using System;
namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
{
- public class SocketSharpLogger : WebSocketSharp.Logging.ILogger
+ public class SocketSharpLogger : SocketHttpListener.Logging.ILogger
{
private readonly ILogger _logger;
diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs
index 9deb34e91..419d145bb 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs
@@ -1,4 +1,4 @@
-using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Logging;
using ServiceStack;
using ServiceStack.Web;
@@ -6,16 +6,14 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
-using System.Threading;
using System.Threading.Tasks;
-using WebSocketSharp.Net;
+using SocketHttpListener.Net;
namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
{
public class WebSocketSharpListener : IHttpListener
{
private HttpListener _listener;
- private readonly ManualResetEventSlim _listenForNextRequest = new ManualResetEventSlim(false);
private readonly ILogger _logger;
private readonly Action<string> _endpointListener;
@@ -43,70 +41,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
_listener.Prefixes.Add(prefix);
}
- _listener.Start();
-
- Task.Factory.StartNew(Listen, TaskCreationOptions.LongRunning);
- }
-
- private bool IsListening
- {
- get { return _listener != null && _listener.IsListening; }
- }
+ _listener.OnContext = ProcessContext;
- // Loop here to begin processing of new requests.
- private void Listen()
- {
- while (IsListening)
- {
- if (_listener == null) return;
- _listenForNextRequest.Reset();
-
- try
- {
- _listener.BeginGetContext(ListenerCallback, _listener);
- _listenForNextRequest.Wait();
- }
- catch (Exception ex)
- {
- _logger.Error("Listen()", ex);
- return;
- }
- if (_listener == null) return;
- }
+ _listener.Start();
}
- // Handle the processing of a request in here.
- private void ListenerCallback(IAsyncResult asyncResult)
+ private void ProcessContext(HttpListenerContext context)
{
- _listenForNextRequest.Set();
-
- var listener = asyncResult.AsyncState as HttpListener;
- HttpListenerContext context;
-
- if (listener == null) return;
- var isListening = listener.IsListening;
-
- try
- {
- if (!isListening)
- {
- _logger.Debug("Ignoring ListenerCallback() as HttpListener is no longer listening"); return;
- }
- // The EndGetContext() method, as with all Begin/End asynchronous methods in the .NET Framework,
- // blocks until there is a request to be processed or some type of data is available.
- context = listener.EndGetContext(asyncResult);
- }
- catch (Exception ex)
- {
- // You will get an exception when httpListener.Stop() is called
- // because there will be a thread stopped waiting on the .EndGetContext()
- // method, and again, that is just the way most Begin/End asynchronous
- // methods of the .NET Framework work.
- var errMsg = ex + ": " + IsListening;
- _logger.Warn(errMsg);
- return;
- }
-
Task.Factory.StartNew(() => InitTask(context));
}
@@ -117,10 +58,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
var task = this.ProcessRequestAsync(context);
task.ContinueWith(x => HandleError(x.Exception, context), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent);
- if (task.Status == TaskStatus.Created)
- {
- task.RunSynchronously();
- }
+ //if (task.Status == TaskStatus.Created)
+ //{
+ // task.RunSynchronously();
+ //}
}
catch (Exception ex)
{
diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs
index 7a5f6fbdc..54c27cf0a 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs
@@ -7,7 +7,7 @@ using MediaBrowser.Model.Logging;
using ServiceStack;
using ServiceStack.Host;
using ServiceStack.Web;
-using WebSocketSharp.Net;
+using SocketHttpListener.Net;
namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
{
diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs
index b725af610..171dacb22 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs
@@ -5,7 +5,7 @@ using MediaBrowser.Model.Logging;
using ServiceStack;
using ServiceStack.Host;
using ServiceStack.Web;
-using HttpListenerResponse = WebSocketSharp.Net.HttpListenerResponse;
+using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse;
namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
{
diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
index 4cb39778c..37a68b52b 100644
--- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
@@ -18,8 +18,8 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Naming.Audio;
using MediaBrowser.Naming.Common;
using MediaBrowser.Naming.IO;
+using MediaBrowser.Naming.TV;
using MediaBrowser.Naming.Video;
-using MediaBrowser.Server.Implementations.Library.Resolvers.TV;
using MediaBrowser.Server.Implementations.Library.Validators;
using MediaBrowser.Server.Implementations.ScheduledTasks;
using System;
@@ -68,6 +68,7 @@ namespace MediaBrowser.Server.Implementations.Library
/// </summary>
/// <value>The entity resolvers enumerable.</value>
private IItemResolver[] EntityResolvers { get; set; }
+ private IMultiItemResolver[] MultiItemResolvers { get; set; }
/// <summary>
/// Gets or sets the comparers.
@@ -196,9 +197,10 @@ namespace MediaBrowser.Server.Implementations.Library
EntityResolutionIgnoreRules = rules.ToArray();
PluginFolderCreators = pluginFolders.ToArray();
EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray();
+ MultiItemResolvers = EntityResolvers.OfType<IMultiItemResolver>().ToArray();
IntroProviders = introProviders.ToArray();
Comparers = itemComparers.ToArray();
-
+
PostscanTasks = postscanTasks.OrderBy(i =>
{
var hasOrder = i as IHasOrder;
@@ -344,7 +346,7 @@ namespace MediaBrowser.Server.Implementations.Library
try
{
- await UpdateItem(season, ItemUpdateType.MetadataDownload, cancellationToken).ConfigureAwait(false);
+ await UpdateItem(season, ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -560,10 +562,9 @@ namespace MediaBrowser.Server.Implementations.Library
}
public BaseItem ResolvePath(FileSystemInfo fileInfo,
- Folder parent = null,
- string collectionType = null)
+ Folder parent = null)
{
- return ResolvePath(fileInfo, new DirectoryService(_logger), parent, collectionType);
+ return ResolvePath(fileInfo, new DirectoryService(_logger), parent);
}
private BaseItem ResolvePath(FileSystemInfo fileInfo, IDirectoryService directoryService, Folder parent = null, string collectionType = null)
@@ -573,10 +574,17 @@ namespace MediaBrowser.Server.Implementations.Library
throw new ArgumentNullException("fileInfo");
}
+ var fullPath = fileInfo.FullName;
+
+ if (string.IsNullOrWhiteSpace(collectionType))
+ {
+ collectionType = GetConfiguredContentType(fullPath);
+ }
+
var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, this, directoryService)
{
Parent = parent,
- Path = fileInfo.FullName,
+ Path = fullPath,
FileInfo = fileInfo,
CollectionType = collectionType
};
@@ -652,9 +660,7 @@ namespace MediaBrowser.Server.Implementations.Library
if (parent != null)
{
- var multiItemResolvers = EntityResolvers.OfType<IMultiItemResolver>();
-
- foreach (var resolver in multiItemResolvers)
+ foreach (var resolver in MultiItemResolvers)
{
var result = resolver.ResolveMultiple(parent, fileList, collectionType, directoryService);
@@ -862,7 +868,7 @@ namespace MediaBrowser.Server.Implementations.Library
var type = typeof(T);
- if (type == typeof(Person) && ConfigurationManager.Configuration.EnablePeoplePrefixSubFolders)
+ if (type == typeof(Person))
{
subFolderPrefix = validFilename.Substring(0, 1);
}
@@ -1546,12 +1552,48 @@ namespace MediaBrowser.Server.Implementations.Library
return ItemRepository.RetrieveItem(id);
}
- /// <summary>
- /// Finds the type of the collection.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <returns>System.String.</returns>
- public string FindCollectionType(BaseItem item)
+ public string GetContentType(BaseItem item)
+ {
+ // Types cannot be overridden, so go from the top down until we find a configured content type
+
+ var type = GetInheritedContentType(item);
+
+ if (!string.IsNullOrWhiteSpace(type))
+ {
+ return type;
+ }
+
+ return GetConfiguredContentType(item);
+ }
+
+ public string GetInheritedContentType(BaseItem item)
+ {
+ var type = GetTopFolderContentType(item);
+
+ if (!string.IsNullOrWhiteSpace(type))
+ {
+ return type;
+ }
+
+ return item.Parents
+ .Select(GetConfiguredContentType)
+ .LastOrDefault(i => !string.IsNullOrWhiteSpace(i));
+ }
+
+ private string GetConfiguredContentType(BaseItem item)
+ {
+ return GetConfiguredContentType(item.ContainingFolderPath);
+ }
+
+ private string GetConfiguredContentType(string path)
+ {
+ var type = ConfigurationManager.Configuration.ContentTypes
+ .FirstOrDefault(i => string.Equals(i.Name, path, StringComparison.OrdinalIgnoreCase) || _fileSystem.ContainsSubPath(i.Name, path));
+
+ return type == null ? null : type.Value;
+ }
+
+ private string GetTopFolderContentType(BaseItem item)
{
while (!(item.Parent is AggregateFolder) && item.Parent != null)
{
@@ -1563,14 +1605,11 @@ namespace MediaBrowser.Server.Implementations.Library
return null;
}
- var collectionTypes = GetUserRootFolder().Children
+ return GetUserRootFolder().Children
.OfType<ICollectionFolder>()
- .Where(i => !string.IsNullOrEmpty(i.CollectionType) && (string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path)))
+ .Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path))
.Select(i => i.CollectionType)
- .Distinct()
- .ToList();
-
- return collectionTypes.Count == 1 ? collectionTypes[0] : null;
+ .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
}
public async Task<UserView> GetNamedView(string name,
@@ -1708,22 +1747,127 @@ namespace MediaBrowser.Server.Implementations.Library
public int? GetSeasonNumberFromPath(string path)
{
- return SeriesResolver.GetSeasonNumberFromPath(path, CollectionType.TvShows);
+ return new SeasonPathParser(new ExtendedNamingOptions(), new RegexProvider()).Parse(path, true).SeasonNumber;
}
- public int? GetSeasonNumberFromEpisodeFile(string path)
+ public bool FillMissingEpisodeNumbersFromPath(Episode episode)
{
- return SeriesResolver.GetSeasonNumberFromEpisodeFile(path);
- }
+ var resolver = new EpisodeResolver(new ExtendedNamingOptions(),
+ new Naming.Logging.NullLogger());
- public int? GetEndingEpisodeNumberFromFile(string path)
- {
- return SeriesResolver.GetEndingEpisodeNumberFromFile(path);
- }
+ var fileType = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd || episode.VideoType == VideoType.HdDvd ?
+ FileInfoType.Directory :
+ FileInfoType.File;
- public int? GetEpisodeNumberFromFile(string path, bool considerSeasonless)
- {
- return SeriesResolver.GetEpisodeNumberFromFile(path, considerSeasonless);
+ var locationType = episode.LocationType;
+
+ var episodeInfo = locationType == LocationType.FileSystem || locationType == LocationType.Offline ?
+ resolver.Resolve(episode.Path, fileType) :
+ new Naming.TV.EpisodeInfo();
+
+ if (episodeInfo == null)
+ {
+ episodeInfo = new Naming.TV.EpisodeInfo();
+ }
+
+ var changed = false;
+
+ if (episodeInfo.IsByDate)
+ {
+ if (episode.IndexNumber.HasValue)
+ {
+ episode.IndexNumber = null;
+ changed = true;
+ }
+
+ if (episode.IndexNumberEnd.HasValue)
+ {
+ episode.IndexNumberEnd = null;
+ changed = true;
+ }
+
+ if (!episode.PremiereDate.HasValue)
+ {
+ if (episodeInfo.Year.HasValue && episodeInfo.Month.HasValue && episodeInfo.Day.HasValue)
+ {
+ episode.PremiereDate = new DateTime(episodeInfo.Year.Value, episodeInfo.Month.Value, episodeInfo.Day.Value).ToUniversalTime();
+ }
+
+ if (episode.PremiereDate.HasValue)
+ {
+ changed = true;
+ }
+ }
+
+ if (!episode.ProductionYear.HasValue)
+ {
+ episode.ProductionYear = episodeInfo.Year;
+
+ if (episode.ProductionYear.HasValue)
+ {
+ changed = true;
+ }
+ }
+
+ if (!episode.ParentIndexNumber.HasValue)
+ {
+ var season = episode.Season;
+
+ if (season != null)
+ {
+ episode.ParentIndexNumber = season.IndexNumber;
+ }
+
+ if (episode.ParentIndexNumber.HasValue)
+ {
+ changed = true;
+ }
+ }
+ }
+ else
+ {
+ if (!episode.IndexNumber.HasValue)
+ {
+ episode.IndexNumber = episodeInfo.EpisodeNumber;
+
+ if (episode.IndexNumber.HasValue)
+ {
+ changed = true;
+ }
+ }
+
+ if (!episode.IndexNumberEnd.HasValue)
+ {
+ episode.IndexNumberEnd = episodeInfo.EndingEpsiodeNumber;
+
+ if (episode.IndexNumberEnd.HasValue)
+ {
+ changed = true;
+ }
+ }
+
+ if (!episode.ParentIndexNumber.HasValue)
+ {
+ episode.ParentIndexNumber = episodeInfo.SeasonNumber;
+
+ if (!episode.ParentIndexNumber.HasValue)
+ {
+ var season = episode.Season;
+
+ if (season != null)
+ {
+ episode.ParentIndexNumber = season.IndexNumber;
+ }
+ }
+
+ if (episode.ParentIndexNumber.HasValue)
+ {
+ changed = true;
+ }
+ }
+ }
+
+ return changed;
}
public ItemLookupInfo ParseName(string name)
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
index f32ed2b20..0f703cb22 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
@@ -37,7 +37,11 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
/// <value>The priority.</value>
public override ResolverPriority Priority
{
- get { return ResolverPriority.Third; } // we need to be ahead of the generic folder resolver but behind the movie one
+ get
+ {
+ // Behind special folder resolver
+ return ResolverPriority.Second;
+ }
}
/// <summary>
@@ -49,21 +53,13 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
{
if (!args.IsDirectory) return null;
- //Avoid mis-identifying top folders
- if (args.Parent == null) return null;
+ // Avoid mis-identifying top folders
if (args.Parent.IsRoot) return null;
if (args.HasParent<MusicAlbum>()) return null;
- // Optimization
- if (args.HasParent<BoxSet>() || args.HasParent<Series>() || args.HasParent<Season>())
- {
- return null;
- }
-
var collectionType = args.GetCollectionType();
- var isMusicMediaFolder = string.Equals(collectionType, CollectionType.Music,
- StringComparison.OrdinalIgnoreCase);
+ var isMusicMediaFolder = string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase);
// If there's a collection type and it's not music, don't allow it.
if (!isMusicMediaFolder)
@@ -125,14 +121,28 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
ILibraryManager libraryManager)
{
var discSubfolderCount = 0;
+ var notMultiDisc = false;
foreach (var fileSystemInfo in list)
{
if ((fileSystemInfo.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
{
- if (allowSubfolders && IsAlbumSubfolder(fileSystemInfo, directoryService, logger, fileSystem, libraryManager))
+ if (allowSubfolders)
{
- discSubfolderCount++;
+ var path = fileSystemInfo.FullName;
+ var isMultiDisc = IsMultiDiscFolder(path);
+ var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryManager);
+
+ if (isMultiDisc && hasMusic)
+ {
+ logger.Debug("Found multi-disc folder: " + path);
+ discSubfolderCount++;
+ }
+ else if (hasMusic)
+ {
+ // If there are folders underneath with music that are not multidisc, then this can't be a multi-disc album
+ notMultiDisc = true;
+ }
}
}
@@ -144,34 +154,15 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
}
}
- return discSubfolderCount > 0;
- }
-
- private static bool IsAlbumSubfolder(FileSystemInfo directory, IDirectoryService directoryService, ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager)
- {
- var path = directory.FullName;
-
- if (IsMultiDiscFolder(path))
+ if (notMultiDisc)
{
- logger.Debug("Found multi-disc folder: " + path);
-
- return ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryManager);
+ return false;
}
- return false;
- }
-
- public static bool IsMultiDiscFolder(string path)
- {
- return IsMultiDiscAlbumFolder(path);
+ return discSubfolderCount > 0 && discSubfolderCount > 10;
}
- /// <summary>
- /// Determines whether [is multi disc album folder] [the specified path].
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns><c>true</c> if [is multi disc album folder] [the specified path]; otherwise, <c>false</c>.</returns>
- private static bool IsMultiDiscAlbumFolder(string path)
+ private static bool IsMultiDiscFolder(string path)
{
var parser = new AlbumParser(new ExtendedNamingOptions(), new Naming.Logging.NullLogger());
var result = parser.ParseMultiPart(path);
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
index 71b6c0843..edbc87415 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
@@ -34,7 +34,11 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
/// <value>The priority.</value>
public override ResolverPriority Priority
{
- get { return ResolverPriority.Third; } // we need to be ahead of the generic folder resolver but behind the movie one
+ get
+ {
+ // Behind special folder resolver
+ return ResolverPriority.Second;
+ }
}
/// <summary>
@@ -46,8 +50,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
{
if (!args.IsDirectory) return null;
- //Avoid mis-identifying top folders
- if (args.Parent == null) return null;
+ // Avoid mis-identifying top folders
if (args.Parent.IsRoot) return null;
// Don't allow nested artists
@@ -56,16 +59,9 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
return null;
}
- // Optimization
- if (args.HasParent<BoxSet>() || args.HasParent<Series>() || args.HasParent<Season>())
- {
- return null;
- }
-
var collectionType = args.GetCollectionType();
- var isMusicMediaFolder = string.Equals(collectionType, CollectionType.Music,
- StringComparison.OrdinalIgnoreCase);
+ var isMusicMediaFolder = string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase);
// If there's a collection type and it's not music, it can't be a series
if (!isMusicMediaFolder)
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs
index 166465f72..34237622d 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs
@@ -1,10 +1,6 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
-using System;
-using System.IO;
-using System.Linq;
namespace MediaBrowser.Server.Implementations.Library.Resolvers
{
@@ -13,13 +9,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
/// </summary>
public class FolderResolver : FolderResolver<Folder>
{
- private readonly IFileSystem _fileSystem;
-
- public FolderResolver(IFileSystem fileSystem)
- {
- _fileSystem = fileSystem;
- }
-
/// <summary>
/// Gets the priority.
/// </summary>
@@ -38,48 +27,11 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
{
if (args.IsDirectory)
{
- if (args.IsPhysicalRoot)
- {
- return new AggregateFolder();
- }
- if (args.IsRoot)
- {
- return new UserRootFolder(); //if we got here and still a root - must be user root
- }
- if (args.IsVf)
- {
- return new CollectionFolder
- {
- CollectionType = GetCollectionType(args)
- };
- }
-
return new Folder();
}
return null;
}
-
- private string GetCollectionType(ItemResolveArgs args)
- {
- return args.FileSystemChildren
- .Where(i =>
- {
-
- try
- {
- return (i.Attributes & FileAttributes.Directory) != FileAttributes.Directory &&
- string.Equals(".collection", i.Extension, StringComparison.OrdinalIgnoreCase);
- }
- catch (IOException)
- {
- return false;
- }
-
- })
- .Select(i => _fileSystem.GetFileNameWithoutExtension(i))
- .FirstOrDefault();
- }
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs
index cc261c3e7..67b9d546f 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs
@@ -4,6 +4,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using System;
using System.IO;
+using System.Linq;
namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
{
@@ -30,12 +31,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
{
return null;
}
-
- // This is a bit of a one-off but it's here to combat MCM's over-aggressive placement of collection.xml files where they don't belong, including in series folders.
- if (args.ContainsMetaFileByName("series.xml"))
- {
- return null;
- }
if (filename.IndexOf("[boxset]", StringComparison.OrdinalIgnoreCase) != -1 ||
args.ContainsFileSystemEntryByName("collection.xml"))
@@ -51,6 +46,17 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
return null;
}
+ private bool IsInvalid(string collectionType)
+ {
+ var validCollectionTypes = new[]
+ {
+ CollectionType.Movies,
+ CollectionType.BoxSets
+ };
+
+ return !validCollectionTypes.Contains(collectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+ }
+
/// <summary>
/// Sets the initial item values.
/// </summary>
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 276b99d3a..587c6668c 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -1,13 +1,9 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
using MediaBrowser.Naming.Common;
using MediaBrowser.Naming.IO;
using MediaBrowser.Naming.Video;
@@ -23,16 +19,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
/// </summary>
public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
{
- private readonly IServerApplicationPaths _applicationPaths;
- private readonly ILogger _logger;
- private readonly IFileSystem _fileSystem;
-
- public MovieResolver(ILibraryManager libraryManager, IServerApplicationPaths applicationPaths, ILogger logger, IFileSystem fileSystem)
- : base(libraryManager)
+ public MovieResolver(ILibraryManager libraryManager) : base(libraryManager)
{
- _applicationPaths = applicationPaths;
- _logger = logger;
- _fileSystem = fileSystem;
}
/// <summary>
@@ -63,12 +51,12 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
{
- return ResolveVideos<MusicVideo>(parent, files, directoryService, collectionType);
+ return ResolveVideos<MusicVideo>(parent, files, directoryService, collectionType, false);
}
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
{
- return ResolveVideos<Video>(parent, files, directoryService, collectionType);
+ return ResolveVideos<Video>(parent, files, directoryService, collectionType, false);
}
if (string.IsNullOrEmpty(collectionType))
@@ -76,22 +64,21 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
// Owned items should just use the plain video type
if (parent == null)
{
- return ResolveVideos<Video>(parent, files, directoryService, collectionType);
+ return ResolveVideos<Video>(parent, files, directoryService, collectionType, false);
}
- return ResolveVideos<Movie>(parent, files, directoryService, collectionType);
+ return ResolveVideos<Video>(parent, files, directoryService, collectionType, false);
}
- if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase) ||
- string.Equals(collectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
{
- return ResolveVideos<Movie>(parent, files, directoryService, collectionType);
+ return ResolveVideos<Movie>(parent, files, directoryService, collectionType, false);
}
return null;
}
- private MultiItemResolverResult ResolveVideos<T>(Folder parent, IEnumerable<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, string collectionType)
+ private MultiItemResolverResult ResolveVideos<T>(Folder parent, IEnumerable<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool suppportMultiEditions)
where T : Video, new()
{
var files = new List<FileSystemInfo>();
@@ -117,7 +104,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
FullName = i.FullName,
Type = FileInfoType.File
- }).ToList()).ToList();
+ }).ToList(), suppportMultiEditions).ToList();
var result = new MultiItemResolverResult
{
@@ -137,7 +124,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
IsInMixedFolder = isInMixedFolder,
ProductionYear = video.Year,
Name = video.Name,
- AdditionalParts = video.Files.Skip(1).Select(i => i.Path).ToList()
+ AdditionalParts = video.Files.Skip(1).Select(i => i.Path).ToList(),
+ LocalAlternateVersions = video.AlternateVersions.Select(i => i.Path).ToList()
};
SetVideoType(videoItem, firstVideo);
@@ -168,12 +156,12 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
{
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
{
- return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType, false);
+ return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType);
}
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
{
- return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType, false);
+ return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType);
}
if (string.IsNullOrEmpty(collectionType))
@@ -184,17 +172,10 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType);
}
- // Since the looping is expensive, this is an optimization to help us avoid it
- if (args.ContainsMetaFileByName("series.xml"))
- {
- return null;
- }
-
- return FindMovie<Movie>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType);
+ return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType);
}
- if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase) ||
- string.Equals(collectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
{
return FindMovie<Movie>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType);
}
@@ -216,8 +197,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
}
// To find a movie file, the collection type must be movies or boxsets
- else if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase) ||
- string.Equals(collectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
{
item = ResolveVideo<Movie>(args, true);
}
@@ -228,7 +208,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
}
else if (string.IsNullOrEmpty(collectionType))
{
- item = ResolveVideo<Movie>(args, false);
+ item = ResolveVideo<Video>(args, false);
}
if (item != null)
@@ -277,9 +257,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
/// <param name="fileSystemEntries">The file system entries.</param>
/// <param name="directoryService">The directory service.</param>
/// <param name="collectionType">Type of the collection.</param>
- /// <param name="supportMultiVersion">if set to <c>true</c> [support multi version].</param>
/// <returns>Movie.</returns>
- private T FindMovie<T>(string path, Folder parent, List<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool supportMultiVersion = true)
+ private T FindMovie<T>(string path, Folder parent, List<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, string collectionType)
where T : Video, new()
{
var multiDiscFolders = new List<FileSystemInfo>();
@@ -325,34 +304,17 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
return movie;
}
}
-
- var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, collectionType);
- // Test for multi-editions
- if (result.Items.Count > 1 && supportMultiVersion)
- {
- var filenamePrefix = Path.GetFileName(path);
+ var supportsMultiVersion = !string.Equals(collectionType, CollectionType.HomeVideos) &&
+ !string.Equals(collectionType, CollectionType.MusicVideos);
- if (!string.IsNullOrWhiteSpace(filenamePrefix))
- {
- if (result.Items.All(i => _fileSystem.GetFileNameWithoutExtension(i.Path).StartsWith(filenamePrefix + " - ", StringComparison.OrdinalIgnoreCase)))
- {
- var movie = (T)result.Items[0];
- movie.Name = filenamePrefix;
- movie.LocalAlternateVersions = result.Items.Skip(1).Select(i => i.Path).ToList();
- movie.IsInMixedFolder = false;
-
- _logger.Debug("Multi-version video found: " + movie.Path);
-
- return movie;
- }
- }
- }
+ var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, collectionType, supportsMultiVersion);
if (result.Items.Count == 1)
{
var movie = (T)result.Items[0];
movie.IsInMixedFolder = false;
+ movie.Name = Path.GetFileName(movie.ContainingFolderPath);
return movie;
}
@@ -453,52 +415,16 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
}
}
- // Don't do any resolving within a series structure
- if (string.IsNullOrEmpty(collectionType))
- {
- if (HasParent<Series>(parent) || HasParent<Season>(parent))
- {
- return true;
- }
-
- // Since the looping is expensive, this is an optimization to help us avoid it
- if (files.Select(i => i.Name).Contains("series.xml", StringComparer.OrdinalIgnoreCase))
- {
- return true;
- }
- }
-
var validCollectionTypes = new[]
{
string.Empty,
CollectionType.Movies,
CollectionType.HomeVideos,
CollectionType.MusicVideos,
- CollectionType.BoxSets,
CollectionType.Movies
};
return !validCollectionTypes.Contains(collectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
}
-
- private bool HasParent<T>(Folder parent)
- where T : Folder
- {
- if (parent != null)
- {
- var item = parent as T;
-
- // Just in case the user decided to nest episodes.
- // Not officially supported but in some cases we can handle it.
- if (item == null)
- {
- item = parent.Parents.OfType<T>().FirstOrDefault();
- }
-
- return item != null;
-
- }
- return false;
- }
}
}
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs
index 2fcfd7086..acae5b801 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs
@@ -1,5 +1,6 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
using System;
using System.IO;
@@ -17,7 +18,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
protected override PhotoAlbum Resolve(ItemResolveArgs args)
{
// Must be an image file within a photo collection
- if (!args.IsRoot && args.IsDirectory && string.Equals(args.GetCollectionType(), CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
+ if (args.IsDirectory && string.Equals(args.GetCollectionType(), CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
{
if (HasPhotos(args))
{
@@ -35,5 +36,14 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
{
return args.FileSystemChildren.Any(i => ((i.Attributes & FileAttributes.Directory) != FileAttributes.Directory) && PhotoResolver.IsImageFile(i.FullName));
}
+
+ public override ResolverPriority Priority
+ {
+ get
+ {
+ // Behind special folder resolver
+ return ResolverPriority.Second;
+ }
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs
index 5580b442c..02960ea7e 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs
@@ -17,8 +17,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
protected override Photo Resolve(ItemResolveArgs args)
{
// Must be an image file within a photo collection
- if (!args.IsDirectory &&
- string.Equals(args.GetCollectionType(), CollectionType.Photos, StringComparison.OrdinalIgnoreCase) &&
+ if (string.Equals(args.GetCollectionType(), CollectionType.Photos, StringComparison.OrdinalIgnoreCase) &&
+ !args.IsDirectory &&
IsImageFile(args.Path))
{
return new Photo
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
new file mode 100644
index 000000000..0a41a6c04
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
@@ -0,0 +1,79 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Resolvers;
+using System;
+using System.IO;
+using System.Linq;
+
+namespace MediaBrowser.Server.Implementations.Library.Resolvers
+{
+ class SpecialFolderResolver : FolderResolver<Folder>
+ {
+ private readonly IFileSystem _fileSystem;
+
+ public SpecialFolderResolver(IFileSystem fileSystem)
+ {
+ _fileSystem = fileSystem;
+ }
+
+ /// <summary>
+ /// Gets the priority.
+ /// </summary>
+ /// <value>The priority.</value>
+ public override ResolverPriority Priority
+ {
+ get { return ResolverPriority.First; }
+ }
+
+ /// <summary>
+ /// Resolves the specified args.
+ /// </summary>
+ /// <param name="args">The args.</param>
+ /// <returns>Folder.</returns>
+ protected override Folder Resolve(ItemResolveArgs args)
+ {
+ if (args.IsDirectory)
+ {
+ if (args.IsPhysicalRoot)
+ {
+ return new AggregateFolder();
+ }
+ if (args.IsRoot)
+ {
+ return new UserRootFolder(); //if we got here and still a root - must be user root
+ }
+ if (args.IsVf)
+ {
+ return new CollectionFolder
+ {
+ CollectionType = GetCollectionType(args)
+ };
+ }
+ }
+
+ return null;
+ }
+
+ private string GetCollectionType(ItemResolveArgs args)
+ {
+ return args.FileSystemChildren
+ .Where(i =>
+ {
+
+ try
+ {
+ return (i.Attributes & FileAttributes.Directory) != FileAttributes.Directory &&
+ string.Equals(".collection", i.Extension, StringComparison.OrdinalIgnoreCase);
+ }
+ catch (IOException)
+ {
+ return false;
+ }
+
+ })
+ .Select(i => _fileSystem.GetFileNameWithoutExtension(i))
+ .FirstOrDefault();
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
index 057425ca9..1a873f01e 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
@@ -37,23 +37,10 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
}
// If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something
- if (season != null || parent is Series || parent.Parents.OfType<Series>().Any())
+ if (season != null || args.HasParent<Series>())
{
var episode = ResolveVideo<Episode>(args, false);
- if (episode != null)
- {
- if (season != null)
- {
- episode.ParentIndexNumber = season.IndexNumber;
- }
-
- if (episode.ParentIndexNumber == null)
- {
- episode.ParentIndexNumber = SeriesResolver.GetSeasonNumberFromEpisodeFile(args.Path);
- }
- }
-
return episode;
}
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
index 058fb1489..80477e567 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
@@ -1,7 +1,8 @@
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Naming.Common;
+using MediaBrowser.Naming.TV;
namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
{
@@ -35,7 +36,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
{
var season = new Season
{
- IndexNumber = SeriesResolver.GetSeasonNumberFromPath(args.Path, CollectionType.TvShows)
+ IndexNumber = new SeasonPathParser(new ExtendedNamingOptions(), new RegexProvider()).Parse(args.Path, true).SeasonNumber
};
if (season.IndexNumber.HasValue && season.IndexNumber.Value == 0)
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
index 72c0bae53..7f1416ad6 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
@@ -1,18 +1,9 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
using System;
-using System.Collections.Generic;
-using System.Globalization;
using System.IO;
-using System.Linq;
-using System.Text.RegularExpressions;
namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
{
@@ -21,17 +12,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
/// </summary>
public class SeriesResolver : FolderResolver<Series>
{
- private readonly IFileSystem _fileSystem;
- private readonly ILogger _logger;
- private readonly ILibraryManager _libraryManager;
-
- public SeriesResolver(IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager)
- {
- _fileSystem = fileSystem;
- _logger = logger;
- _libraryManager = libraryManager;
- }
-
/// <summary>
/// Gets the priority.
/// </summary>
@@ -53,33 +33,16 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
{
if (args.IsDirectory)
{
- // Avoid expensive tests against VF's and all their children by not allowing this
- if (args.Parent == null || args.Parent.IsRoot)
- {
- return null;
- }
-
- // Optimization to avoid running these tests against Seasons
- if (args.HasParent<Series>() || args.HasParent<Season>() || args.HasParent<MusicArtist>() || args.HasParent<MusicAlbum>())
- {
- return null;
- }
-
var collectionType = args.GetCollectionType();
- var isTvShowsFolder = string.Equals(collectionType, CollectionType.TvShows,
- StringComparison.OrdinalIgnoreCase);
-
// If there's a collection type and it's not tv, it can't be a series
- if (!string.IsNullOrEmpty(collectionType) &&
- !isTvShowsFolder &&
- !string.Equals(collectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase))
- {
- return null;
- }
-
- if (IsSeriesFolder(args.Path, collectionType, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager))
+ if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
+ if (args.HasParent<Series>())
+ {
+ return null;
+ }
+
return new Series
{
Path = args.Path,
@@ -92,434 +55,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
}
/// <summary>
- /// Determines whether [is series folder] [the specified path].
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="collectionType">Type of the collection.</param>
- /// <param name="fileSystemChildren">The file system children.</param>
- /// <param name="directoryService">The directory service.</param>
- /// <param name="fileSystem">The file system.</param>
- /// <param name="logger">The logger.</param>
- /// <param name="libraryManager">The library manager.</param>
- /// <returns><c>true</c> if [is series folder] [the specified path]; otherwise, <c>false</c>.</returns>
- public static bool IsSeriesFolder(string path, string collectionType, IEnumerable<FileSystemInfo> fileSystemChildren, IDirectoryService directoryService, IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager)
- {
- foreach (var child in fileSystemChildren)
- {
- var attributes = child.Attributes;
-
- if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
- {
- //logger.Debug("Igoring series file or folder marked hidden: {0}", child.FullName);
- continue;
- }
-
- // Can't enforce this because files saved by Bitcasa are always marked System
- //if ((attributes & FileAttributes.System) == FileAttributes.System)
- //{
- // logger.Debug("Igoring series subfolder marked system: {0}", child.FullName);
- // continue;
- //}
-
- if ((attributes & FileAttributes.Directory) == FileAttributes.Directory)
- {
- if (IsSeasonFolder(child.FullName, collectionType, directoryService, fileSystem))
- {
- //logger.Debug("{0} is a series because of season folder {1}.", path, child.FullName);
- return true;
- }
- }
- else
- {
- var fullName = child.FullName;
-
- if (libraryManager.IsVideoFile(fullName) || IsVideoPlaceHolder(fullName))
- {
- var isTvShowsFolder = string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase);
-
- if (GetEpisodeNumberFromFile(fullName, isTvShowsFolder).HasValue)
- {
- return true;
- }
- }
- }
- }
-
- logger.Debug("{0} is not a series folder.", path);
- return false;
- }
-
- /// <summary>
- /// Determines whether [is place holder] [the specified path].
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns><c>true</c> if [is place holder] [the specified path]; otherwise, <c>false</c>.</returns>
- /// <exception cref="System.ArgumentNullException">path</exception>
- private static bool IsVideoPlaceHolder(string path)
- {
- if (string.IsNullOrEmpty(path))
- {
- throw new ArgumentNullException("path");
- }
-
- var extension = Path.GetExtension(path);
-
- return string.Equals(extension, ".disc", StringComparison.OrdinalIgnoreCase);
- }
-
- /// <summary>
- /// Determines whether [is season folder] [the specified path].
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="collectionType">Type of the collection.</param>
- /// <param name="directoryService">The directory service.</param>
- /// <param name="fileSystem">The file system.</param>
- /// <returns><c>true</c> if [is season folder] [the specified path]; otherwise, <c>false</c>.</returns>
- private static bool IsSeasonFolder(string path, string collectionType, IDirectoryService directoryService, IFileSystem fileSystem)
- {
- var seasonNumber = GetSeasonNumberFromPath(path, collectionType);
- var hasSeasonNumber = seasonNumber != null;
-
- if (!hasSeasonNumber)
- {
- return false;
- }
-
- //// It's a season folder if it's named as such and does not contain any audio files, apart from theme.mp3
- //foreach (var fileSystemInfo in directoryService.GetFileSystemEntries(path))
- //{
- // var attributes = fileSystemInfo.Attributes;
-
- // if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
- // {
- // continue;
- // }
-
- // // Can't enforce this because files saved by Bitcasa are always marked System
- // //if ((attributes & FileAttributes.System) == FileAttributes.System)
- // //{
- // // continue;
- // //}
-
- // if ((attributes & FileAttributes.Directory) == FileAttributes.Directory)
- // {
- // //if (IsBadFolder(fileSystemInfo.Name))
- // //{
- // // return false;
- // //}
- // }
- // else
- // {
- // if (EntityResolutionHelper.IsAudioFile(fileSystemInfo.FullName) &&
- // !string.Equals(fileSystem.GetFileNameWithoutExtension(fileSystemInfo), BaseItem.ThemeSongFilename))
- // {
- // return false;
- // }
- // }
- //}
-
- return true;
- }
-
- /// <summary>
- /// A season folder must contain one of these somewhere in the name
- /// </summary>
- private static readonly string[] SeasonFolderNames =
- {
- "season",
- "sæson",
- "temporada",
- "saison",
- "staffel",
- "series",
- "сезон"
- };
-
- /// <summary>
- /// Used to detect paths that represent episodes, need to make sure they don't also
- /// match movie titles like "2001 A Space..."
- /// Currently we limit the numbers here to 2 digits to try and avoid this
- /// </summary>
- private static readonly Regex[] EpisodeExpressions =
- {
- new Regex(
- @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})[^\\\/]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*(\\|\/)[sS](?<seasonnumber>\d{1,4})[x,X]?[eE](?<epnumber>\d{1,3})[^\\\/]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))[^\\\/]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})[^\\\/]*$",
- RegexOptions.Compiled)
- };
- private static readonly Regex[] MultipleEpisodeExpressions =
- {
- new Regex(
- @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )\d{1,4}[eExX](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )\d{1,4}[xX][eE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})(-[xE]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )\d{1,4}[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )\d{1,4}[xX][eE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
- RegexOptions.Compiled),
- new Regex(
- @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$",
- RegexOptions.Compiled)
- };
-
- /// <summary>
- /// To avoid the following matching movies they are only valid when contained in a folder which has been matched as a being season, or the media type is TV series
- /// </summary>
- private static readonly Regex[] EpisodeExpressionsWithoutSeason =
- {
- new Regex(
- @".*[\\\/](?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\.\w+$",
- RegexOptions.Compiled),
- // "01.avi"
- new Regex(
- @".*(\\|\/)(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\s?-\s?[^\\\/]*$",
- RegexOptions.Compiled),
- // "01 - blah.avi", "01-blah.avi"
- new Regex(
- @".*(\\|\/)(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\.[^\\\/]+$",
- RegexOptions.Compiled),
- // "01.blah.avi"
- new Regex(
- @".*[\\\/][^\\\/]* - (?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/]*$",
- RegexOptions.Compiled),
- // "blah - 01.avi", "blah 2 - 01.avi", "blah - 01 blah.avi", "blah 2 - 01 blah", "blah - 01 - blah.avi", "blah 2 - 01 - blah"
- };
-
- public static int? GetSeasonNumberFromPath(string path)
- {
- return GetSeasonNumberFromPath(path, CollectionType.TvShows);
- }
-
- /// <summary>
- /// Gets the season number from path.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="collectionType">Type of the collection.</param>
- /// <returns>System.Nullable{System.Int32}.</returns>
- public static int? GetSeasonNumberFromPath(string path, string collectionType)
- {
- var filename = Path.GetFileName(path);
-
- if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
- {
- if (string.Equals(filename, "specials", StringComparison.OrdinalIgnoreCase))
- {
- return 0;
- }
- }
-
- int val;
- if (int.TryParse(filename, NumberStyles.Integer, CultureInfo.InvariantCulture, out val))
- {
- return val;
- }
-
- if (filename.StartsWith("s", StringComparison.OrdinalIgnoreCase))
- {
- var testFilename = filename.Substring(1);
-
- if (int.TryParse(testFilename, NumberStyles.Integer, CultureInfo.InvariantCulture, out val))
- {
- return val;
- }
- }
-
- // Look for one of the season folder names
- foreach (var name in SeasonFolderNames)
- {
- var index = filename.IndexOf(name, StringComparison.OrdinalIgnoreCase);
-
- if (index != -1)
- {
- return GetSeasonNumberFromPathSubstring(filename.Substring(index + name.Length));
- }
- }
-
- return null;
- }
-
- /// <summary>
- /// Extracts the season number from the second half of the Season folder name (everything after "Season", or "Staffel")
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns>System.Nullable{System.Int32}.</returns>
- private static int? GetSeasonNumberFromPathSubstring(string path)
- {
- var numericStart = -1;
- var length = 0;
-
- // Find out where the numbers start, and then keep going until they end
- for (var i = 0; i < path.Length; i++)
- {
- if (char.IsNumber(path, i))
- {
- if (numericStart == -1)
- {
- numericStart = i;
- }
- length++;
- }
- else if (numericStart != -1)
- {
- break;
- }
- }
-
- if (numericStart == -1)
- {
- return null;
- }
-
- return int.Parse(path.Substring(numericStart, length), CultureInfo.InvariantCulture);
- }
-
- /// <summary>
- /// Episodes the number from file.
- /// </summary>
- /// <param name="fullPath">The full path.</param>
- /// <param name="considerSeasonlessNames">if set to <c>true</c> [is in season].</param>
- /// <returns>System.String.</returns>
- public static int? GetEpisodeNumberFromFile(string fullPath, bool considerSeasonlessNames)
- {
- string fl = fullPath.ToLower();
- foreach (var r in EpisodeExpressions)
- {
- Match m = r.Match(fl);
- if (m.Success)
- return ParseEpisodeNumber(m.Groups["epnumber"].Value);
- }
- if (considerSeasonlessNames)
- {
- var match = EpisodeExpressionsWithoutSeason.Select(r => r.Match(fl))
- .FirstOrDefault(m => m.Success);
-
- if (match != null)
- {
- return ParseEpisodeNumber(match.Groups["epnumber"].Value);
- }
- }
-
- return null;
- }
-
- public static int? GetEndingEpisodeNumberFromFile(string fullPath)
- {
- var fl = fullPath.ToLower();
- foreach (var r in MultipleEpisodeExpressions)
- {
- var m = r.Match(fl);
- if (m.Success && !string.IsNullOrEmpty(m.Groups["endingepnumber"].Value))
- return ParseEpisodeNumber(m.Groups["endingepnumber"].Value);
- }
- foreach (var r in EpisodeExpressionsWithoutSeason)
- {
- var m = r.Match(fl);
- if (m.Success && !string.IsNullOrEmpty(m.Groups["endingepnumber"].Value))
- return ParseEpisodeNumber(m.Groups["endingepnumber"].Value);
- }
- return null;
- }
-
- /// <summary>
- /// Seasons the number from episode file.
- /// </summary>
- /// <param name="fullPath">The full path.</param>
- /// <returns>System.String.</returns>
- public static int? GetSeasonNumberFromEpisodeFile(string fullPath)
- {
- string fl = fullPath.ToLower();
- foreach (var r in EpisodeExpressions)
- {
- Match m = r.Match(fl);
- if (m.Success)
- {
- Group g = m.Groups["seasonnumber"];
- if (g != null)
- {
- var val = g.Value;
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- int num;
-
- if (int.TryParse(val, NumberStyles.Integer, UsCulture, out num))
- {
- return num;
- }
- }
- }
- return null;
- }
- }
- return null;
- }
-
- public static string GetSeriesNameFromEpisodeFile(string fullPath)
- {
- var fl = fullPath.ToLower();
- foreach (var r in EpisodeExpressions)
- {
- var m = r.Match(fl);
- if (m.Success)
- {
- var g = m.Groups["seriesname"];
- if (g != null)
- {
- var val = g.Value;
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- return val;
- }
- }
- return null;
- }
- }
- return null;
- }
-
- private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- private static int? ParseEpisodeNumber(string val)
- {
- int num;
-
- if (!string.IsNullOrEmpty(val) && int.TryParse(val, NumberStyles.Integer, UsCulture, out num))
- {
- return num;
- }
-
- return null;
- }
-
- /// <summary>
/// Sets the initial item values.
/// </summary>
/// <param name="item">The item.</param>
diff --git a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs
index bd845ef4a..071031b25 100644
--- a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs
+++ b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs
@@ -1,5 +1,6 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying;
@@ -105,7 +106,7 @@ namespace MediaBrowser.Server.Implementations.Library
if (query.IncludeMedia)
{
// Add search hints based on item name
- hints.AddRange(items.Where(i => !string.IsNullOrWhiteSpace(i.Name)).Select(item =>
+ hints.AddRange(items.Where(i => !string.IsNullOrWhiteSpace(i.Name) && IncludeInSearch(i)).Select(item =>
{
var index = GetIndex(item.Name, searchTerm, terms);
@@ -289,6 +290,20 @@ namespace MediaBrowser.Server.Implementations.Library
return Task.FromResult(returnValue);
}
+ private bool IncludeInSearch(BaseItem item)
+ {
+ var episode = item as Episode;
+
+ if (episode != null)
+ {
+ if (episode.IsVirtualUnaired || episode.IsMissingEpisode)
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
/// <summary>
/// Gets the index.
/// </summary>
diff --git a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs
index 3f132ab7f..ed3503c1b 100644
--- a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs
@@ -1,6 +1,8 @@
using System.Collections.Generic;
using MediaBrowser.Common.Events;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
@@ -23,9 +25,11 @@ namespace MediaBrowser.Server.Implementations.Library
private readonly ConcurrentDictionary<string, UserItemData> _userData = new ConcurrentDictionary<string, UserItemData>();
private readonly ILogger _logger;
+ private readonly IServerConfigurationManager _config;
- public UserDataManager(ILogManager logManager)
+ public UserDataManager(ILogManager logManager, IServerConfigurationManager config)
{
+ _config = config;
_logger = logManager.GetLogger(GetType().Name);
}
@@ -35,22 +39,6 @@ namespace MediaBrowser.Server.Implementations.Library
/// <value>The repository.</value>
public IUserDataRepository Repository { get; set; }
- /// <summary>
- /// Saves the user data.
- /// </summary>
- /// <param name="userId">The user id.</param>
- /// <param name="item">The item.</param>
- /// <param name="userData">The user data.</param>
- /// <param name="reason">The reason.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- /// <exception cref="System.ArgumentNullException">userData
- /// or
- /// cancellationToken
- /// or
- /// userId
- /// or
- /// key</exception>
public async Task SaveUserData(Guid userId, IHasUserData item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken)
{
if (userData == null)
@@ -219,5 +207,59 @@ namespace MediaBrowser.Server.Implementations.Library
Key = data.Key
};
}
+
+ public bool UpdatePlayState(BaseItem item, UserItemData data, long positionTicks)
+ {
+ var playedToCompletion = false;
+
+ var hasRuntime = item.RunTimeTicks.HasValue && item.RunTimeTicks > 0;
+
+ // If a position has been reported, and if we know the duration
+ if (positionTicks > 0 && hasRuntime)
+ {
+ var pctIn = Decimal.Divide(positionTicks, item.RunTimeTicks.Value) * 100;
+
+ // Don't track in very beginning
+ if (pctIn < _config.Configuration.MinResumePct)
+ {
+ positionTicks = 0;
+ }
+
+ // If we're at the end, assume completed
+ else if (pctIn > _config.Configuration.MaxResumePct || positionTicks >= item.RunTimeTicks.Value)
+ {
+ positionTicks = 0;
+ data.Played = playedToCompletion = true;
+ }
+
+ else
+ {
+ // Enforce MinResumeDuration
+ var durationSeconds = TimeSpan.FromTicks(item.RunTimeTicks.Value).TotalSeconds;
+
+ if (durationSeconds < _config.Configuration.MinResumeDurationSeconds)
+ {
+ positionTicks = 0;
+ data.Played = playedToCompletion = true;
+ }
+ }
+ }
+ else if (!hasRuntime)
+ {
+ // If we don't know the runtime we'll just have to assume it was fully played
+ data.Played = playedToCompletion = true;
+ positionTicks = 0;
+ }
+
+ if (item is Audio)
+ {
+ positionTicks = 0;
+ }
+
+ data.PlaybackPositionTicks = positionTicks;
+
+ return playedToCompletion;
+ }
+
}
}
diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs
index 791cae553..503af4970 100644
--- a/MediaBrowser.Server.Implementations/Library/UserManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs
@@ -1,5 +1,4 @@
-using System.Collections.Concurrent;
-using MediaBrowser.Common.Events;
+using MediaBrowser.Common.Events;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
@@ -63,6 +62,7 @@ namespace MediaBrowser.Server.Implementations.Library
public event EventHandler<GenericEventArgs<User>> UserPasswordChanged;
private readonly IXmlSerializer _xmlSerializer;
+ private readonly IJsonSerializer _jsonSerializer;
private readonly INetworkManager _networkManager;
@@ -71,13 +71,7 @@ namespace MediaBrowser.Server.Implementations.Library
private readonly Func<IConnectManager> _connectFactory;
private readonly IServerApplicationHost _appHost;
- /// <summary>
- /// Initializes a new instance of the <see cref="UserManager" /> class.
- /// </summary>
- /// <param name="logger">The logger.</param>
- /// <param name="configurationManager">The configuration manager.</param>
- /// <param name="userRepository">The user repository.</param>
- public UserManager(ILogger logger, IServerConfigurationManager configurationManager, IUserRepository userRepository, IXmlSerializer xmlSerializer, INetworkManager networkManager, Func<IImageProcessor> imageProcessorFactory, Func<IDtoService> dtoServiceFactory, Func<IConnectManager> connectFactory, IServerApplicationHost appHost)
+ public UserManager(ILogger logger, IServerConfigurationManager configurationManager, IUserRepository userRepository, IXmlSerializer xmlSerializer, INetworkManager networkManager, Func<IImageProcessor> imageProcessorFactory, Func<IDtoService> dtoServiceFactory, Func<IConnectManager> connectFactory, IServerApplicationHost appHost, IJsonSerializer jsonSerializer)
{
_logger = logger;
UserRepository = userRepository;
@@ -87,6 +81,7 @@ namespace MediaBrowser.Server.Implementations.Library
_dtoServiceFactory = dtoServiceFactory;
_connectFactory = connectFactory;
_appHost = appHost;
+ _jsonSerializer = jsonSerializer;
ConfigurationManager = configurationManager;
Users = new List<User>();
@@ -164,6 +159,11 @@ namespace MediaBrowser.Server.Implementations.Library
public async Task Initialize()
{
Users = await LoadUsers().ConfigureAwait(false);
+
+ foreach (var user in Users.ToList())
+ {
+ await DoPolicyMigration(user).ConfigureAwait(false);
+ }
}
public Task<bool> AuthenticateUser(string username, string passwordSha1, string remoteEndPoint)
@@ -171,6 +171,38 @@ namespace MediaBrowser.Server.Implementations.Library
return AuthenticateUser(username, passwordSha1, null, remoteEndPoint);
}
+ public bool IsValidUsername(string username)
+ {
+ // Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)
+ return username.All(IsValidCharacter);
+ }
+
+ private bool IsValidCharacter(char i)
+ {
+ return char.IsLetterOrDigit(i) || char.Equals(i, '-') || char.Equals(i, '_') || char.Equals(i, '\'') ||
+ char.Equals(i, '.');
+ }
+
+ public string MakeValidUsername(string username)
+ {
+ if (IsValidUsername(username))
+ {
+ return username;
+ }
+
+ // Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)
+ var builder = new StringBuilder();
+
+ foreach (var c in username)
+ {
+ if (IsValidCharacter(c))
+ {
+ builder.Append(c);
+ }
+ }
+ return builder.ToString();
+ }
+
public async Task<bool> AuthenticateUser(string username, string passwordSha1, string passwordMd5, string remoteEndPoint)
{
if (string.IsNullOrWhiteSpace(username))
@@ -178,14 +210,15 @@ namespace MediaBrowser.Server.Implementations.Library
throw new ArgumentNullException("username");
}
- var user = Users.FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
+ var user = Users
+ .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
if (user == null)
{
throw new SecurityException("Invalid username or password entered.");
}
- if (user.Configuration.IsDisabled)
+ if (user.Policy.IsDisabled)
{
throw new SecurityException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name));
}
@@ -203,20 +236,6 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
- // Maybe user accidently entered connect credentials. let's be flexible
- if (!success && user.ConnectLinkType.HasValue && !string.IsNullOrWhiteSpace(passwordMd5))
- {
- try
- {
- await _connectFactory().Authenticate(user.ConnectUserName, passwordMd5).ConfigureAwait(false);
- success = true;
- }
- catch
- {
-
- }
- }
-
// Update LastActivityDate and LastLoginDate, then save
if (success)
{
@@ -273,7 +292,7 @@ namespace MediaBrowser.Server.Implementations.Library
// There always has to be at least one user.
if (users.Count == 0)
{
- var name = Environment.UserName;
+ var name = MakeValidUsername(Environment.UserName);
var user = InstantiateNewUser(name, false);
@@ -283,14 +302,42 @@ namespace MediaBrowser.Server.Implementations.Library
users.Add(user);
- user.Configuration.IsAdministrator = true;
- user.Configuration.EnableRemoteControlOfOtherUsers = true;
- UpdateConfiguration(user, user.Configuration);
+ user.Policy.IsAdministrator = true;
+ user.Policy.EnableRemoteControlOfOtherUsers = true;
+ await UpdateUserPolicy(user, user.Policy, false).ConfigureAwait(false);
}
return users;
}
+ private async Task DoPolicyMigration(User user)
+ {
+ if (!user.Configuration.HasMigratedToPolicy)
+ {
+ user.Policy.AccessSchedules = user.Configuration.AccessSchedules;
+ user.Policy.BlockedChannels = user.Configuration.BlockedChannels;
+ user.Policy.BlockedMediaFolders = user.Configuration.BlockedMediaFolders;
+ user.Policy.BlockedTags = user.Configuration.BlockedTags;
+ user.Policy.BlockUnratedItems = user.Configuration.BlockUnratedItems;
+ user.Policy.EnableContentDeletion = user.Configuration.EnableContentDeletion;
+ user.Policy.EnableLiveTvAccess = user.Configuration.EnableLiveTvAccess;
+ user.Policy.EnableLiveTvManagement = user.Configuration.EnableLiveTvManagement;
+ user.Policy.EnableMediaPlayback = user.Configuration.EnableMediaPlayback;
+ user.Policy.EnableRemoteControlOfOtherUsers = user.Configuration.EnableRemoteControlOfOtherUsers;
+ user.Policy.EnableSharedDeviceControl = user.Configuration.EnableSharedDeviceControl;
+ user.Policy.EnableUserPreferenceAccess = user.Configuration.EnableUserPreferenceAccess;
+ user.Policy.IsAdministrator = user.Configuration.IsAdministrator;
+ user.Policy.IsDisabled = user.Configuration.IsDisabled;
+ user.Policy.IsHidden = user.Configuration.IsHidden;
+ user.Policy.MaxParentalRating = user.Configuration.MaxParentalRating;
+
+ await UpdateUserPolicy(user.Id.ToString("N"), user.Policy);
+
+ user.Configuration.HasMigratedToPolicy = true;
+ await UpdateConfiguration(user, user.Configuration, true).ConfigureAwait(false);
+ }
+ }
+
public UserDto GetUserDto(User user, string remoteEndPoint = null)
{
if (user == null)
@@ -449,6 +496,11 @@ namespace MediaBrowser.Server.Implementations.Library
throw new ArgumentNullException("name");
}
+ if (!IsValidUsername(name))
+ {
+ throw new ArgumentException("Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)");
+ }
+
if (Users.Any(u => u.Name.Equals(name, StringComparison.OrdinalIgnoreCase)))
{
throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", name));
@@ -509,7 +561,7 @@ namespace MediaBrowser.Server.Implementations.Library
throw new ArgumentException(string.Format("The user '{0}' cannot be deleted because there must be at least one user in the system.", user.Name));
}
- if (user.Configuration.IsAdministrator && allUsers.Count(i => i.Configuration.IsAdministrator) == 1)
+ if (user.Policy.IsAdministrator && allUsers.Count(i => i.Policy.IsAdministrator) == 1)
{
throw new ArgumentException(string.Format("The user '{0}' cannot be deleted because there must be at least one admin user in the system.", user.Name));
}
@@ -518,17 +570,17 @@ namespace MediaBrowser.Server.Implementations.Library
try
{
- await UserRepository.DeleteUser(user, CancellationToken.None).ConfigureAwait(false);
+ var configPath = GetConfigurationFilePath(user);
- var path = user.ConfigurationFilePath;
+ await UserRepository.DeleteUser(user, CancellationToken.None).ConfigureAwait(false);
try
{
- File.Delete(path);
+ File.Delete(configPath);
}
catch (IOException ex)
{
- _logger.ErrorException("Error deleting file {0}", ex, path);
+ _logger.ErrorException("Error deleting file {0}", ex, configPath);
}
DeleteUserPolicy(user);
@@ -613,15 +665,6 @@ namespace MediaBrowser.Server.Implementations.Library
};
}
- public void UpdateConfiguration(User user, UserConfiguration newConfiguration)
- {
- var xmlPath = user.ConfigurationFilePath;
- Directory.CreateDirectory(Path.GetDirectoryName(xmlPath));
- _xmlSerializer.SerializeToFile(newConfiguration, xmlPath);
-
- EventHelper.FireEventIfNotNull(UserConfigurationUpdated, this, new GenericEventArgs<User> { Argument = user }, _logger);
- }
-
private string PasswordResetFile
{
get { return Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "passwordreset.txt"); }
@@ -689,7 +732,7 @@ namespace MediaBrowser.Server.Implementations.Library
string pinFile = null;
DateTime? expirationDate = null;
- if (user != null && !user.Configuration.IsAdministrator)
+ if (user != null && !user.Policy.IsAdministrator)
{
action = ForgotPasswordAction.ContactAdmin;
}
@@ -784,28 +827,72 @@ namespace MediaBrowser.Server.Implementations.Library
return (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), path);
}
}
+ catch (DirectoryNotFoundException)
+ {
+ return GetDefaultPolicy(user);
+ }
+ catch (FileNotFoundException)
+ {
+ return GetDefaultPolicy(user);
+ }
catch (Exception ex)
{
_logger.ErrorException("Error reading policy file: {0}", ex, path);
- return new UserPolicy
- {
- EnableSync = !user.ConnectLinkType.HasValue || user.ConnectLinkType.Value != UserLinkType.Guest
- };
+ return GetDefaultPolicy(user);
}
}
+ private UserPolicy GetDefaultPolicy(User user)
+ {
+ return new UserPolicy
+ {
+ EnableSync = true
+ };
+ }
+
private readonly object _policySyncLock = new object();
- public async Task UpdateUserPolicy(string userId, UserPolicy userPolicy)
+ public Task UpdateUserPolicy(string userId, UserPolicy userPolicy)
{
var user = GetUserById(userId);
+ return UpdateUserPolicy(user, userPolicy, true);
+ }
+
+ private async Task UpdateUserPolicy(User user, UserPolicy userPolicy, bool fireEvent)
+ {
+ // The xml serializer will output differently if the type is not exact
+ if (userPolicy.GetType() != typeof(UserPolicy))
+ {
+ var json = _jsonSerializer.SerializeToString(userPolicy);
+ userPolicy = _jsonSerializer.DeserializeFromString<UserPolicy>(json);
+ }
+
+ var updateConfig = user.Policy.IsAdministrator != userPolicy.IsAdministrator ||
+ user.Policy.EnableLiveTvManagement != userPolicy.EnableLiveTvManagement ||
+ user.Policy.EnableLiveTvAccess != userPolicy.EnableLiveTvAccess ||
+ user.Policy.EnableMediaPlayback != userPolicy.EnableMediaPlayback ||
+ user.Policy.EnableContentDeletion != userPolicy.EnableContentDeletion;
+
var path = GetPolifyFilePath(user);
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
+
lock (_policySyncLock)
{
_xmlSerializer.SerializeToFile(userPolicy, path);
user.Policy = userPolicy;
}
+
+ if (updateConfig)
+ {
+ user.Configuration.IsAdministrator = user.Policy.IsAdministrator;
+ user.Configuration.EnableLiveTvManagement = user.Policy.EnableLiveTvManagement;
+ user.Configuration.EnableLiveTvAccess = user.Policy.EnableLiveTvAccess;
+ user.Configuration.EnableMediaPlayback = user.Policy.EnableMediaPlayback;
+ user.Configuration.EnableContentDeletion = user.Policy.EnableContentDeletion;
+
+ await UpdateConfiguration(user, user.Configuration, true).ConfigureAwait(false);
+ }
}
private void DeleteUserPolicy(User user)
@@ -833,5 +920,69 @@ namespace MediaBrowser.Server.Implementations.Library
{
return Path.Combine(user.ConfigurationDirectoryPath, "policy.xml");
}
+
+ private string GetConfigurationFilePath(User user)
+ {
+ return Path.Combine(user.ConfigurationDirectoryPath, "config.xml");
+ }
+
+ public UserConfiguration GetUserConfiguration(User user)
+ {
+ var path = GetConfigurationFilePath(user);
+
+ try
+ {
+ lock (_configSyncLock)
+ {
+ return (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), path);
+ }
+ }
+ catch (DirectoryNotFoundException)
+ {
+ return new UserConfiguration();
+ }
+ catch (FileNotFoundException)
+ {
+ return new UserConfiguration();
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error reading policy file: {0}", ex, path);
+
+ return new UserConfiguration();
+ }
+ }
+
+ private readonly object _configSyncLock = new object();
+ public Task UpdateConfiguration(string userId, UserConfiguration config)
+ {
+ var user = GetUserById(userId);
+ return UpdateConfiguration(user, config, true);
+ }
+
+ private async Task UpdateConfiguration(User user, UserConfiguration config, bool fireEvent)
+ {
+ var path = GetConfigurationFilePath(user);
+
+ // The xml serializer will output differently if the type is not exact
+ if (config.GetType() != typeof (UserConfiguration))
+ {
+ var json = _jsonSerializer.SerializeToString(config);
+ config = _jsonSerializer.DeserializeFromString<UserConfiguration>(json);
+ }
+
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
+
+ lock (_configSyncLock)
+ {
+ _xmlSerializer.SerializeToFile(config, path);
+ user.Configuration = config;
+ }
+
+ if (fireEvent)
+ {
+ EventHelper.FireEventIfNotNull(UserConfigurationUpdated, this, new GenericEventArgs<User> { Argument = user }, _logger);
+ }
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
index 83fe992b6..96498563e 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -309,16 +309,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return Task.FromResult(true);
}
- private async Task RefreshIfNeeded(LiveTvProgram program, CancellationToken cancellationToken)
+ private readonly Task _cachedTask = Task.FromResult(true);
+ private Task RefreshIfNeeded(LiveTvProgram program, CancellationToken cancellationToken)
{
- if (_refreshedPrograms.ContainsKey(program.Id))
+ if (!_refreshedPrograms.ContainsKey(program.Id))
{
- return;
+ _refreshedPrograms.TryAdd(program.Id, true);
+ return program.RefreshMetadata(cancellationToken);
}
- _refreshedPrograms.TryAdd(program.Id, true);
-
- await program.RefreshMetadata(cancellationToken).ConfigureAwait(false);
+ return _cachedTask;
}
public async Task<ILiveTvRecording> GetInternalRecording(string id, CancellationToken cancellationToken)
@@ -1846,7 +1846,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
private bool IsLiveTvEnabled(User user)
{
- return user.Configuration.EnableLiveTvAccess && ActiveService != null;
+ return user.Policy.EnableLiveTvAccess && ActiveService != null;
}
public IEnumerable<User> GetEnabledUsers()
@@ -1854,7 +1854,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var service = ActiveService;
return _userManager.Users
- .Where(i => i.Configuration.EnableLiveTvAccess && service != null);
+ .Where(i => i.Policy.EnableLiveTvAccess && service != null);
}
/// <summary>
@@ -1872,12 +1872,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
var user = string.IsNullOrEmpty(userId) ? null : _userManager.GetUserById(userId);
- // Get everything
- var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
-
var folder = await GetInternalLiveTvFolder(userId, cancellationToken).ConfigureAwait(false);
- return _dtoService.GetBaseItemDto(folder, fields, user);
+ return _dtoService.GetBaseItemDto(folder, new DtoOptions(), user);
}
public async Task<Folder> GetInternalLiveTvFolder(string userId, CancellationToken cancellationToken)
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json
index 81f648728..288bb6da8 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json
@@ -40,6 +40,13 @@
"LabelStopping": "Stopping",
"LabelCancelled": "(cancelled)",
"LabelFailed": "(failed)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "\u062a\u062e\u0632\u064a\u0646",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Library Access",
+ "HeaderChannelAccess": "Channel Access",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(Aborted by server shutdown)",
"LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.",
"HeaderDeleteTaskTrigger": "Delete Task Trigger",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Move right",
"ButtonBrowseOnlineImages": "Browse online images",
"HeaderDeleteItem": "Delete Item",
- "ConfirmDeleteItem": "Are you sure you wish to delete this item from your library?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "Please enter a name or an external Id.",
"MessageValueNotCorrect": "The value entered is not correct. Please try again.",
"MessageItemSaved": "Item saved.",
@@ -396,6 +403,7 @@
"LabelYear": "Year:",
"LabelDateOfBirth": "Date of birth:",
"LabelBirthYear": "Birth year:",
+ "LabelBirthDate": "Birth date:",
"LabelDeathDate": "Death date:",
"HeaderRemoveMediaLocation": "Remove Media Location",
"MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?",
@@ -409,9 +417,9 @@
"ButtonRename": "Rename",
"ButtonChangeType": "Change type",
"HeaderMediaLocations": "Media Locations",
- "LabelFolderTypeValue": "Folder type: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Optional: Path substitution can map server paths to network shares that clients can access for direct playback.",
- "FolderTypeMixed": "Mixed movies & tv",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "Movies",
"FolderTypeMusic": "Music",
"FolderTypeAdultVideos": "Adult videos",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Home videos",
"FolderTypeGames": "Games",
"FolderTypeBooks": "Books",
- "FolderTypeTvShows": "TV shows",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Movies",
"TabSeries": "Series",
"TabEpisodes": "\u0627\u0644\u062d\u0644\u0642\u0627\u062a",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "and easily controls other devices and Media Browser apps",
"MessageEnjoyYourStay": "Enjoy your stay",
"DashboardTourDashboard": "The server dashboard allows you to monitor your server and your users. You'll always know who is doing what and where they are.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Easily create user accounts for your friends and family, each with their own permissions, library access, parental controls and more.",
"DashboardTourCinemaMode": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
"DashboardTourChapters": "Enable chapter image generation for your videos for a more pleasing presentation while viewing.",
@@ -642,7 +651,20 @@
"OptionLow": "Low",
"HeaderSettings": "Settings",
"OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
+ "OptionAutomaticallySyncNewContentHelp": "New content added to this category will be automatically synced to the device.",
"OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
- "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
+ "LabelItemLimit": "Item limit:",
+ "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+ "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+ "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json
index d0dfd608b..91c227bca 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json
@@ -40,6 +40,13 @@
"LabelStopping": "Stopping",
"LabelCancelled": "(cancelled)",
"LabelFailed": "(failed)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "Save",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Library Access",
+ "HeaderChannelAccess": "Channel Access",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(Aborted by server shutdown)",
"LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.",
"HeaderDeleteTaskTrigger": "Delete Task Trigger",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Move right",
"ButtonBrowseOnlineImages": "Browse online images",
"HeaderDeleteItem": "Delete Item",
- "ConfirmDeleteItem": "Are you sure you wish to delete this item from your library?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "Please enter a name or an external Id.",
"MessageValueNotCorrect": "The value entered is not correct. Please try again.",
"MessageItemSaved": "Item saved.",
@@ -396,6 +403,7 @@
"LabelYear": "Year:",
"LabelDateOfBirth": "Date of birth:",
"LabelBirthYear": "Birth year:",
+ "LabelBirthDate": "Birth date:",
"LabelDeathDate": "Death date:",
"HeaderRemoveMediaLocation": "Remove Media Location",
"MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?",
@@ -409,9 +417,9 @@
"ButtonRename": "Rename",
"ButtonChangeType": "Change type",
"HeaderMediaLocations": "Media Locations",
- "LabelFolderTypeValue": "Folder type: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Optional: Path substitution can map server paths to network shares that clients can access for direct playback.",
- "FolderTypeMixed": "Mixed movies & tv",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "Movies",
"FolderTypeMusic": "Music",
"FolderTypeAdultVideos": "Adult videos",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Home videos",
"FolderTypeGames": "Games",
"FolderTypeBooks": "Books",
- "FolderTypeTvShows": "TV shows",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Movies",
"TabSeries": "Series",
"TabEpisodes": "Episodes",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "and easily controls other devices and Media Browser apps",
"MessageEnjoyYourStay": "Enjoy your stay",
"DashboardTourDashboard": "The server dashboard allows you to monitor your server and your users. You'll always know who is doing what and where they are.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Easily create user accounts for your friends and family, each with their own permissions, library access, parental controls and more.",
"DashboardTourCinemaMode": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
"DashboardTourChapters": "Enable chapter image generation for your videos for a more pleasing presentation while viewing.",
@@ -642,7 +651,20 @@
"OptionLow": "Low",
"HeaderSettings": "Settings",
"OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
+ "OptionAutomaticallySyncNewContentHelp": "New content added to this category will be automatically synced to the device.",
"OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
- "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
+ "LabelItemLimit": "Item limit:",
+ "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+ "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+ "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json
index 9f2da7797..282f0b299 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json
@@ -40,6 +40,13 @@
"LabelStopping": "Stopping",
"LabelCancelled": "(zru\u0161eno)",
"LabelFailed": "(failed)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "Ulo\u017eit",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Library Access",
+ "HeaderChannelAccess": "Channel Access",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(Aborted by server shutdown)",
"LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.",
"HeaderDeleteTaskTrigger": "Delete Task Trigger",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Move right",
"ButtonBrowseOnlineImages": "Browse online images",
"HeaderDeleteItem": "Delete Item",
- "ConfirmDeleteItem": "Are you sure you wish to delete this item from your library?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "Please enter a name or an external Id.",
"MessageValueNotCorrect": "The value entered is not correct. Please try again.",
"MessageItemSaved": "Item saved.",
@@ -396,6 +403,7 @@
"LabelYear": "Year:",
"LabelDateOfBirth": "Date of birth:",
"LabelBirthYear": "Birth year:",
+ "LabelBirthDate": "Birth date:",
"LabelDeathDate": "Death date:",
"HeaderRemoveMediaLocation": "Remove Media Location",
"MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?",
@@ -409,9 +417,9 @@
"ButtonRename": "Rename",
"ButtonChangeType": "Change type",
"HeaderMediaLocations": "Media Locations",
- "LabelFolderTypeValue": "Folder type: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Optional: Path substitution can map server paths to network shares that clients can access for direct playback.",
- "FolderTypeMixed": "Mixed movies & tv",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "Movies",
"FolderTypeMusic": "Music",
"FolderTypeAdultVideos": "Adult videos",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Home videos",
"FolderTypeGames": "Games",
"FolderTypeBooks": "Books",
- "FolderTypeTvShows": "TV shows",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Filmy",
"TabSeries": "S\u00e9rie",
"TabEpisodes": "Epizody",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "and easily controls other devices and Media Browser apps",
"MessageEnjoyYourStay": "Enjoy your stay",
"DashboardTourDashboard": "The server dashboard allows you to monitor your server and your users. You'll always know who is doing what and where they are.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Easily create user accounts for your friends and family, each with their own permissions, library access, parental controls and more.",
"DashboardTourCinemaMode": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
"DashboardTourChapters": "Enable chapter image generation for your videos for a more pleasing presentation while viewing.",
@@ -642,7 +651,20 @@
"OptionLow": "Low",
"HeaderSettings": "Settings",
"OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
+ "OptionAutomaticallySyncNewContentHelp": "New content added to this category will be automatically synced to the device.",
"OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
- "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
+ "LabelItemLimit": "Item limit:",
+ "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+ "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+ "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json
index 4bd7d3103..5442195b3 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json
@@ -40,6 +40,13 @@
"LabelStopping": "Stopping",
"LabelCancelled": "(cancelled)",
"LabelFailed": "(failed)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "Gem",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Library Access",
+ "HeaderChannelAccess": "Channel Access",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(Aborted by server shutdown)",
"LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.",
"HeaderDeleteTaskTrigger": "Delete Task Trigger",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Move right",
"ButtonBrowseOnlineImages": "Browse online images",
"HeaderDeleteItem": "Delete Item",
- "ConfirmDeleteItem": "Are you sure you wish to delete this item from your library?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "Please enter a name or an external Id.",
"MessageValueNotCorrect": "The value entered is not correct. Please try again.",
"MessageItemSaved": "Item saved.",
@@ -396,6 +403,7 @@
"LabelYear": "Year:",
"LabelDateOfBirth": "Date of birth:",
"LabelBirthYear": "Birth year:",
+ "LabelBirthDate": "Birth date:",
"LabelDeathDate": "Death date:",
"HeaderRemoveMediaLocation": "Remove Media Location",
"MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?",
@@ -409,9 +417,9 @@
"ButtonRename": "Rename",
"ButtonChangeType": "Change type",
"HeaderMediaLocations": "Media Locations",
- "LabelFolderTypeValue": "Folder type: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Optional: Path substitution can map server paths to network shares that clients can access for direct playback.",
- "FolderTypeMixed": "Mixed movies & tv",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "Movies",
"FolderTypeMusic": "Music",
"FolderTypeAdultVideos": "Adult videos",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Home videos",
"FolderTypeGames": "Games",
"FolderTypeBooks": "Books",
- "FolderTypeTvShows": "TV shows",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Film",
"TabSeries": "Serier",
"TabEpisodes": "Episoder",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "and easily controls other devices and Media Browser apps",
"MessageEnjoyYourStay": "Enjoy your stay",
"DashboardTourDashboard": "The server dashboard allows you to monitor your server and your users. You'll always know who is doing what and where they are.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Easily create user accounts for your friends and family, each with their own permissions, library access, parental controls and more.",
"DashboardTourCinemaMode": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
"DashboardTourChapters": "Enable chapter image generation for your videos for a more pleasing presentation while viewing.",
@@ -642,7 +651,20 @@
"OptionLow": "Low",
"HeaderSettings": "Settings",
"OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
+ "OptionAutomaticallySyncNewContentHelp": "New content added to this category will be automatically synced to the device.",
"OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
- "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
+ "LabelItemLimit": "Item limit:",
+ "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+ "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+ "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json
index d8a8036b0..364916307 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json
@@ -40,6 +40,13 @@
"LabelStopping": "Stoppe",
"LabelCancelled": "(abgebrochen)",
"LabelFailed": "(fehlgeschlagen)",
+ "ButtonHelp": "Hilfe",
+ "ButtonSave": "Speichern",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Bibliothekszugriff",
+ "HeaderChannelAccess": "Channelzugriff",
+ "HeaderDeviceAccess": "Ger\u00e4te Zugang",
+ "HeaderSelectDevices": "Ger\u00e4t w\u00e4hlen",
"LabelAbortedByServerShutdown": "(Durch herunterfahrenden Server abgebrochen)",
"LabelScheduledTaskLastRan": "Zuletzt ausgef\u00fchrt vor: {0}. Ben\u00f6tigte Zeit: {1}.",
"HeaderDeleteTaskTrigger": "Entferne Aufgabenausl\u00f6ser",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Nach rechts",
"ButtonBrowseOnlineImages": "Durchsuche Onlinebilder",
"HeaderDeleteItem": "L\u00f6sche Element",
- "ConfirmDeleteItem": "Bist du dir sicher dieses Element aus deiner Bibliothek zu l\u00f6schen?",
+ "ConfirmDeleteItem": "L\u00f6schen dieses Eintrages bedeutet das L\u00f6schen der Datei und das Entfernen aus der Medien-Bibliothek. M\u00f6chten Sie wirklich fortfahren?",
"MessagePleaseEnterNameOrId": "Bitte gib einen Namen oder eine externe Id an.",
"MessageValueNotCorrect": "Der eingegeben Wert ist nicht korrekt. Bitte versuche es noch einmal.",
"MessageItemSaved": "Element gespeichert",
@@ -396,6 +403,7 @@
"LabelYear": "Jahr:",
"LabelDateOfBirth": "Geburtsatum:",
"LabelBirthYear": "Geburtsjahr:",
+ "LabelBirthDate": "Geburtsdatum:",
"LabelDeathDate": "Todesdatum:",
"HeaderRemoveMediaLocation": "Entferne Medienquelle",
"MessageConfirmRemoveMediaLocation": "Bist du dir sicher diese Medienquelle entfernen zu wollen?",
@@ -409,9 +417,9 @@
"ButtonRename": "Umbenennen",
"ButtonChangeType": "\u00c4ndere Typ",
"HeaderMediaLocations": "Medienquellen",
- "LabelFolderTypeValue": "Verzeichnistyp: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Optional: Die Pfadersetzung kann Serverpfade zu Netzwerkfreigaben umleiten, die von Endger\u00e4ten f\u00fcr die direkte Wiedergabe genutzt werden k\u00f6nnen.",
- "FolderTypeMixed": "Filme & Serien gemischt",
+ "FolderTypeUnset": "Keine Auswahl (gemischter Inhalt)",
"FolderTypeMovies": "Filme",
"FolderTypeMusic": "Musik",
"FolderTypeAdultVideos": "Videos f\u00fcr Erwachsene",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Heimvideos",
"FolderTypeGames": "Spiele",
"FolderTypeBooks": "B\u00fccher",
- "FolderTypeTvShows": "TV Serien",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Filme",
"TabSeries": "Serie",
"TabEpisodes": "Episoden",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "und steuert einfach andere Ger\u00e4te und Media Browser Anwendungen",
"MessageEnjoyYourStay": "Genie\u00dfe deinen Aufenthalt",
"DashboardTourDashboard": "Die Server\u00fcbersicht erlaubt es dir deinen Server und dessen Benutzer im Blick zu behalten. Somit wei\u00dft du immer wer gerade was macht und wo er sich befindet.",
+ "DashboardTourHelp": "Die In-App-Hilfe Schaltfl\u00e4che bietet eine schnelle M\u00f6glichkeit um eine Wiki-Seite zum aktuellen Inhalt zu \u00f6ffnen.",
"DashboardTourUsers": "Erstelle einfach Benutzeraccounts f\u00fcr Freunde und Familie. Jeder mit seinen individuellen Einstellungen bei Berechtigungen, Blibliothekenzugriff, Kindersicherung und mehr.",
"DashboardTourCinemaMode": "Der Kino-Modus bringt das Kinoerlebnis direkt in dein Wohnzimmer, mit der F\u00e4higkeit Trailer und benutzerdefinierte Intros vor dem Hauptfilm zu spielen.",
"DashboardTourChapters": "Aktiviere die Bildgenerierung f\u00fcr die Kapitel deiner Videos f\u00fcr eine bessere Darstellung w\u00e4hrend des Ansehens.",
@@ -642,7 +651,20 @@
"OptionLow": "Niedrig",
"HeaderSettings": "Einstellungen",
"OptionAutomaticallySyncNewContent": "Synchronisiere neue Inhalte automatisch",
- "OptionAutomaticallySyncNewContentHelp": "Neu hinzugef\u00fcgte Inhalte zu diesen Verzeichnissen werden automatisch zum Ger\u00e4t synchronisiert.",
+ "OptionAutomaticallySyncNewContentHelp": "Neue Inhalte dieser Kategorie werden automatisch mit dem Ger\u00e4t synchronisiert.",
"OptionSyncUnwatchedVideosOnly": "Synchronisiere nur ungesehene Videos.",
- "OptionSyncUnwatchedVideosOnlyHelp": "Nur ungesehene Video werden synchronisiert. Videos werden entfernt sobald diese auf dem Ger\u00e4t angeschaut wurden."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Nur ungesehene Video werden synchronisiert. Videos werden entfernt sobald diese auf dem Ger\u00e4t angeschaut wurden.",
+ "LabelItemLimit": "Maximale Anzahl:",
+ "LabelItemLimitHelp": "Optional. Legen Sie die maximale Anzahl der zu synchronisierenden Eintr\u00e4ge fest.",
+ "MessageBookPluginRequired": "Setzt die Installation des Bookshelf-Plugins voraus.",
+ "MessageGamePluginRequired": "Setzt die Installation des GameBrowser-Plugins voraus.",
+ "MessageUnsetContentHelp": "Inhalte werden als Verzeichnisse dargestellt. F\u00fcr eine besser Anzeige nutzen Sie nach M\u00f6glichkeit den Meta-Data Manager und w\u00e4hlen Sie einen Medien-Typen f\u00fcr Unterverzeichnisse.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json
index 20c9de1ef..353df0c37 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json
@@ -40,6 +40,13 @@
"LabelStopping": "Stopping",
"LabelCancelled": "(cancelled)",
"LabelFailed": "(failed)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "\u0391\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03cd\u03c3\u03c4\u03b5",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Library Access",
+ "HeaderChannelAccess": "Channel Access",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(Aborted by server shutdown)",
"LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.",
"HeaderDeleteTaskTrigger": "Delete Task Trigger",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Move right",
"ButtonBrowseOnlineImages": "Browse online images",
"HeaderDeleteItem": "Delete Item",
- "ConfirmDeleteItem": "Are you sure you wish to delete this item from your library?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "Please enter a name or an external Id.",
"MessageValueNotCorrect": "The value entered is not correct. Please try again.",
"MessageItemSaved": "Item saved.",
@@ -396,6 +403,7 @@
"LabelYear": "Year:",
"LabelDateOfBirth": "Date of birth:",
"LabelBirthYear": "Birth year:",
+ "LabelBirthDate": "Birth date:",
"LabelDeathDate": "Death date:",
"HeaderRemoveMediaLocation": "Remove Media Location",
"MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?",
@@ -409,9 +417,9 @@
"ButtonRename": "Rename",
"ButtonChangeType": "Change type",
"HeaderMediaLocations": "Media Locations",
- "LabelFolderTypeValue": "Folder type: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Optional: Path substitution can map server paths to network shares that clients can access for direct playback.",
- "FolderTypeMixed": "Mixed movies & tv",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "Movies",
"FolderTypeMusic": "Music",
"FolderTypeAdultVideos": "Adult videos",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Home videos",
"FolderTypeGames": "Games",
"FolderTypeBooks": "Books",
- "FolderTypeTvShows": "TV shows",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Movies",
"TabSeries": "Series",
"TabEpisodes": "\u0395\u03c0\u03b5\u03b9\u03c3\u03cc\u03b4\u03b9\u03b1",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "and easily controls other devices and Media Browser apps",
"MessageEnjoyYourStay": "Enjoy your stay",
"DashboardTourDashboard": "The server dashboard allows you to monitor your server and your users. You'll always know who is doing what and where they are.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Easily create user accounts for your friends and family, each with their own permissions, library access, parental controls and more.",
"DashboardTourCinemaMode": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
"DashboardTourChapters": "Enable chapter image generation for your videos for a more pleasing presentation while viewing.",
@@ -642,7 +651,20 @@
"OptionLow": "Low",
"HeaderSettings": "Settings",
"OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
+ "OptionAutomaticallySyncNewContentHelp": "New content added to this category will be automatically synced to the device.",
"OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
- "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
+ "LabelItemLimit": "Item limit:",
+ "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+ "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+ "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json
index fec45e70c..30a7867d8 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json
@@ -40,6 +40,13 @@
"LabelStopping": "Stopping",
"LabelCancelled": "(cancelled)",
"LabelFailed": "(failed)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "Save",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Library Access",
+ "HeaderChannelAccess": "Channel Access",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(Aborted by server shutdown)",
"LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.",
"HeaderDeleteTaskTrigger": "Delete Task Trigger",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Move right",
"ButtonBrowseOnlineImages": "Browse online images",
"HeaderDeleteItem": "Delete Item",
- "ConfirmDeleteItem": "Are you sure you wish to delete this item from your library?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "Please enter a name or an external Id.",
"MessageValueNotCorrect": "The value entered is not correct. Please try again.",
"MessageItemSaved": "Item saved.",
@@ -396,6 +403,7 @@
"LabelYear": "Year:",
"LabelDateOfBirth": "Date of birth:",
"LabelBirthYear": "Birth year:",
+ "LabelBirthDate": "Birth date:",
"LabelDeathDate": "Death date:",
"HeaderRemoveMediaLocation": "Remove Media Location",
"MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?",
@@ -409,9 +417,9 @@
"ButtonRename": "Rename",
"ButtonChangeType": "Change type",
"HeaderMediaLocations": "Media Locations",
- "LabelFolderTypeValue": "Folder type: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Optional: Path substitution can map server paths to network shares that clients can access for direct playback.",
- "FolderTypeMixed": "Mixed movies & tv",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "Movies",
"FolderTypeMusic": "Music",
"FolderTypeAdultVideos": "Adult videos",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Home videos",
"FolderTypeGames": "Games",
"FolderTypeBooks": "Books",
- "FolderTypeTvShows": "TV shows",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Movies",
"TabSeries": "Series",
"TabEpisodes": "Episodes",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "and easily controls other devices and Media Browser apps",
"MessageEnjoyYourStay": "Enjoy your stay",
"DashboardTourDashboard": "The server dashboard allows you to monitor your server and your users. You'll always know who is doing what and where they are.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Easily create user accounts for your friends and family, each with their own permissions, library access, parental controls and more.",
"DashboardTourCinemaMode": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
"DashboardTourChapters": "Enable chapter image generation for your videos for a more pleasing presentation while viewing.",
@@ -642,7 +651,20 @@
"OptionLow": "Low",
"HeaderSettings": "Settings",
"OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
+ "OptionAutomaticallySyncNewContentHelp": "New content added to this category will be automatically synced to the device.",
"OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
- "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
+ "LabelItemLimit": "Item limit:",
+ "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+ "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+ "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json
index 369299be0..5482a2b47 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json
@@ -40,6 +40,13 @@
"LabelStopping": "Stopping",
"LabelCancelled": "(cancelled)",
"LabelFailed": "(failed)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "Save",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Library Access",
+ "HeaderChannelAccess": "Channel Access",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(Aborted by server shutdown)",
"LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.",
"HeaderDeleteTaskTrigger": "Delete Task Trigger",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Move right",
"ButtonBrowseOnlineImages": "Browse online images",
"HeaderDeleteItem": "Delete Item",
- "ConfirmDeleteItem": "Are you sure you wish to delete this item from your library?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "Please enter a name or an external Id.",
"MessageValueNotCorrect": "The value entered is not correct. Please try again.",
"MessageItemSaved": "Item saved.",
@@ -396,6 +403,7 @@
"LabelYear": "Year:",
"LabelDateOfBirth": "Date of birth:",
"LabelBirthYear": "Birth year:",
+ "LabelBirthDate": "Birth date:",
"LabelDeathDate": "Death date:",
"HeaderRemoveMediaLocation": "Remove Media Location",
"MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?",
@@ -409,9 +417,9 @@
"ButtonRename": "Rename",
"ButtonChangeType": "Change type",
"HeaderMediaLocations": "Media Locations",
- "LabelFolderTypeValue": "Folder type: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Optional: Path substitution can map server paths to network shares that clients can access for direct playback.",
- "FolderTypeMixed": "Mixed movies & tv",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "Movies",
"FolderTypeMusic": "Music",
"FolderTypeAdultVideos": "Adult videos",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Home videos",
"FolderTypeGames": "Games",
"FolderTypeBooks": "Books",
- "FolderTypeTvShows": "TV shows",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Movies",
"TabSeries": "Series",
"TabEpisodes": "Episodes",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "and easily controls other devices and Media Browser apps",
"MessageEnjoyYourStay": "Enjoy your stay",
"DashboardTourDashboard": "The server dashboard allows you to monitor your server and your users. You'll always know who is doing what and where they are.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Easily create user accounts for your friends and family, each with their own permissions, library access, parental controls and more.",
"DashboardTourCinemaMode": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
"DashboardTourChapters": "Enable chapter image generation for your videos for a more pleasing presentation while viewing.",
@@ -642,7 +651,20 @@
"OptionLow": "Low",
"HeaderSettings": "Settings",
"OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
+ "OptionAutomaticallySyncNewContentHelp": "New content added to this category will be automatically synced to the device.",
"OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
- "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
+ "LabelItemLimit": "Item limit:",
+ "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+ "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+ "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/es.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/es.json
index d2957340c..82497d2c3 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/es.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/es.json
@@ -40,6 +40,13 @@
"LabelStopping": "Deteniendo",
"LabelCancelled": "(cancelado)",
"LabelFailed": "(failed)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "Grabar",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Library Access",
+ "HeaderChannelAccess": "Channel Access",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(Abortado por cierre del servidor)",
"LabelScheduledTaskLastRan": "\u00daltima ejecuci\u00f3n {0}, teniendo {1}.",
"HeaderDeleteTaskTrigger": "Eliminar tarea de activaci\u00f3n",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Mover derecha",
"ButtonBrowseOnlineImages": "Navegar im\u00e1genes online",
"HeaderDeleteItem": "Borrar elemento",
- "ConfirmDeleteItem": "\u00bfEst\u00e1 seguro que desea borrar este elemento de su biblioteca?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "Introduzca un nombre o un identificador externo.",
"MessageValueNotCorrect": "El valor introducido no es correcto. Intentelo de nuevo.",
"MessageItemSaved": "Elemento grabado.",
@@ -396,6 +403,7 @@
"LabelYear": "Year:",
"LabelDateOfBirth": "Date of birth:",
"LabelBirthYear": "Birth year:",
+ "LabelBirthDate": "Birth date:",
"LabelDeathDate": "Death date:",
"HeaderRemoveMediaLocation": "Remove Media Location",
"MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?",
@@ -409,9 +417,9 @@
"ButtonRename": "Rename",
"ButtonChangeType": "Change type",
"HeaderMediaLocations": "Media Locations",
- "LabelFolderTypeValue": "Folder type: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Optional: Path substitution can map server paths to network shares that clients can access for direct playback.",
- "FolderTypeMixed": "Mixed movies & tv",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "Movies",
"FolderTypeMusic": "Music",
"FolderTypeAdultVideos": "Adult videos",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Home videos",
"FolderTypeGames": "Games",
"FolderTypeBooks": "Books",
- "FolderTypeTvShows": "TV shows",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Pel\u00edculas",
"TabSeries": "Series",
"TabEpisodes": "Episodios",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "and easily controls other devices and Media Browser apps",
"MessageEnjoyYourStay": "Enjoy your stay",
"DashboardTourDashboard": "The server dashboard allows you to monitor your server and your users. You'll always know who is doing what and where they are.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Easily create user accounts for your friends and family, each with their own permissions, library access, parental controls and more.",
"DashboardTourCinemaMode": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
"DashboardTourChapters": "Enable chapter image generation for your videos for a more pleasing presentation while viewing.",
@@ -642,7 +651,20 @@
"OptionLow": "Low",
"HeaderSettings": "Settings",
"OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
+ "OptionAutomaticallySyncNewContentHelp": "New content added to this category will be automatically synced to the device.",
"OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
- "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
+ "LabelItemLimit": "Item limit:",
+ "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+ "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+ "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json
index fda5b65b3..ee1c088e1 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json
@@ -40,6 +40,13 @@
"LabelStopping": "Deteniendo",
"LabelCancelled": "(cancelado)",
"LabelFailed": "(Fallido)",
+ "ButtonHelp": "Ayuda",
+ "ButtonSave": "Guardar",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Acceso a la Biblioteca",
+ "HeaderChannelAccess": "Acceso a los Canales",
+ "HeaderDeviceAccess": "Acceso a Dispositivos",
+ "HeaderSelectDevices": "Seleccionar Dispositivos",
"LabelAbortedByServerShutdown": "(Abortada por apagado del servidor)",
"LabelScheduledTaskLastRan": "Ejecutado hace {0}, tomando {1}.",
"HeaderDeleteTaskTrigger": "Borrar Disparador de Tarea",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Mover a la derecha",
"ButtonBrowseOnlineImages": "Buscar im\u00e1genes en l\u00ednea",
"HeaderDeleteItem": "Eliminar \u00cdtem",
- "ConfirmDeleteItem": "\u00bfEst\u00e1 seguro de querer eleiminar este \u00edtem de su biblioteca?",
+ "ConfirmDeleteItem": "Al eliminar este \u00edtem se eliminar\u00e1 tanto del sistema de archivos como de su biblioteca de medios. \u00bfEsta seguro de querer continuar?",
"MessagePleaseEnterNameOrId": "Por favor ingrese un nombre o id externo.",
"MessageValueNotCorrect": "El valor ingresado no es correcto. Intente nuevamente por favor.",
"MessageItemSaved": "\u00cdtem guardado.",
@@ -396,6 +403,7 @@
"LabelYear": "A\u00f1o:",
"LabelDateOfBirth": "Fecha de nacimiento:",
"LabelBirthYear": "A\u00f1o de nacimiento:",
+ "LabelBirthDate": "Fecha de Nacimiento:",
"LabelDeathDate": "Fecha de defunci\u00f3n:",
"HeaderRemoveMediaLocation": "Eliminar Ubicaci\u00f3n de Medios",
"MessageConfirmRemoveMediaLocation": "\u00bfEst\u00e1 seguro de querer eliminar esta ubicaci\u00f3n?",
@@ -409,9 +417,9 @@
"ButtonRename": "Renombrar",
"ButtonChangeType": "Cambiar tipo",
"HeaderMediaLocations": "Ubicaciones de Medios",
- "LabelFolderTypeValue": "Tipo de carpeta: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Opcional: La sustituci\u00f3n de trayectoras puede mapear trayectorias del servidor a recursos de red comaprtidos que los clientes pueden acceder para reproducir de manera directa.",
- "FolderTypeMixed": "Pel\u00edculas y TV mezclados",
+ "FolderTypeUnset": "No establecido (contenido mixto)",
"FolderTypeMovies": "Pel\u00edculas",
"FolderTypeMusic": "M\u00fasica",
"FolderTypeAdultVideos": "Videos para adultos",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Videos caseros",
"FolderTypeGames": "Juegos",
"FolderTypeBooks": "Libros",
- "FolderTypeTvShows": "Programas de TV",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Pel\u00edculas",
"TabSeries": "Series",
"TabEpisodes": "Episodios",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "y controla f\u00e1cilmente otros dispositivos y apps de Media Browser",
"MessageEnjoyYourStay": "Disfrute su visita",
"DashboardTourDashboard": "El panel de control del servidor le permite monitorear su servidor y sus usuarios. Siempre sabr\u00e1 quien est\u00e1 haciendo qu\u00e9 y donde se encuentran.",
+ "DashboardTourHelp": "La ayuda dentro de la app proporciona botones simples para abrir p\u00e1ginas de la wiki relacionadas con el contenido en pantalla.",
"DashboardTourUsers": "Cree cuentas f\u00e1cilmente para sus amigos y familia, cada una con sus propios permisos, accesos a la biblioteca, controles parentales y m\u00e1s.",
"DashboardTourCinemaMode": "El modo cine trae la experiencia del cine directo a su sala de TV con la capacidad de reproducir avances e intros personalizados antes de la presentaci\u00f3n estelar.",
"DashboardTourChapters": "Active la generaci\u00f3n de im\u00e1genes de cap\u00edtulos de sus videos para una presentaci\u00f3n m\u00e1s agradable al desplegar.",
@@ -642,7 +651,20 @@
"OptionLow": "Baja",
"HeaderSettings": "Configuraci\u00f3n",
"OptionAutomaticallySyncNewContent": "Sincronizar autom\u00e1ticamente nuevos contenidos",
- "OptionAutomaticallySyncNewContentHelp": "Los contenidos nuevos que sean agregados a estas carpetas ser\u00e1n sincronizados autom\u00e1ticamente con el dispositivo.",
+ "OptionAutomaticallySyncNewContentHelp": "Los contenidos nuevos agregados a esta categor\u00eda ser\u00e1n sincronizados autom\u00e1ticamente con el dispositivo.",
"OptionSyncUnwatchedVideosOnly": "Sincronizar \u00fanicamente videos no vistos",
- "OptionSyncUnwatchedVideosOnlyHelp": "Solamente los videos a\u00fan no vistos ser\u00e1n sincronizados, se eliminar\u00e1n los videos del dispositivo conforme \u00e9stos sean vistos."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Solamente los videos a\u00fan no vistos ser\u00e1n sincronizados, se eliminar\u00e1n los videos del dispositivo conforme \u00e9stos sean vistos.",
+ "LabelItemLimit": "L\u00edmite de \u00cdtems:",
+ "LabelItemLimitHelp": "Opcional. Establece un l\u00edmite en el n\u00famero de \u00edtems que ser\u00e1n sincronizados.",
+ "MessageBookPluginRequired": "Requiere instalaci\u00f3n del complemento Bookshelf",
+ "MessageGamePluginRequired": "Requiere instalaci\u00f3n del complemento de GameBrowser",
+ "MessageUnsetContentHelp": "El contenido ser\u00e1 desplegado como carpetas simples. Para mejores resultados utilice el administrador de metadatos para establecer los tipos de contenido para las sub-carpetas.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/fi.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/fi.json
index e9c6263e2..fce8a5f51 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/fi.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/fi.json
@@ -40,6 +40,13 @@
"LabelStopping": "Stopping",
"LabelCancelled": "(cancelled)",
"LabelFailed": "(failed)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "Tallenna",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Library Access",
+ "HeaderChannelAccess": "Channel Access",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(Aborted by server shutdown)",
"LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.",
"HeaderDeleteTaskTrigger": "Delete Task Trigger",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Move right",
"ButtonBrowseOnlineImages": "Browse online images",
"HeaderDeleteItem": "Delete Item",
- "ConfirmDeleteItem": "Are you sure you wish to delete this item from your library?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "Please enter a name or an external Id.",
"MessageValueNotCorrect": "The value entered is not correct. Please try again.",
"MessageItemSaved": "Item saved.",
@@ -396,6 +403,7 @@
"LabelYear": "Year:",
"LabelDateOfBirth": "Date of birth:",
"LabelBirthYear": "Birth year:",
+ "LabelBirthDate": "Birth date:",
"LabelDeathDate": "Death date:",
"HeaderRemoveMediaLocation": "Remove Media Location",
"MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?",
@@ -409,9 +417,9 @@
"ButtonRename": "Rename",
"ButtonChangeType": "Change type",
"HeaderMediaLocations": "Media Locations",
- "LabelFolderTypeValue": "Folder type: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Optional: Path substitution can map server paths to network shares that clients can access for direct playback.",
- "FolderTypeMixed": "Mixed movies & tv",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "Movies",
"FolderTypeMusic": "Music",
"FolderTypeAdultVideos": "Adult videos",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Home videos",
"FolderTypeGames": "Games",
"FolderTypeBooks": "Books",
- "FolderTypeTvShows": "TV shows",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Movies",
"TabSeries": "Series",
"TabEpisodes": "Episodes",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "and easily controls other devices and Media Browser apps",
"MessageEnjoyYourStay": "Enjoy your stay",
"DashboardTourDashboard": "The server dashboard allows you to monitor your server and your users. You'll always know who is doing what and where they are.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Easily create user accounts for your friends and family, each with their own permissions, library access, parental controls and more.",
"DashboardTourCinemaMode": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
"DashboardTourChapters": "Enable chapter image generation for your videos for a more pleasing presentation while viewing.",
@@ -642,7 +651,20 @@
"OptionLow": "Low",
"HeaderSettings": "Settings",
"OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
+ "OptionAutomaticallySyncNewContentHelp": "New content added to this category will be automatically synced to the device.",
"OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
- "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
+ "LabelItemLimit": "Item limit:",
+ "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+ "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+ "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json
index d3c95bfa4..5138b669d 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json
@@ -40,6 +40,13 @@
"LabelStopping": "En cours d'arr\u00eat",
"LabelCancelled": "(annul\u00e9)",
"LabelFailed": "(\u00e9chou\u00e9)",
+ "ButtonHelp": "Aide",
+ "ButtonSave": "Sauvegarder",
+ "MessageNoSyncJobsFound": "Aucune t\u00e2che de synchronisation trouv\u00e9e. Vous pouvez cr\u00e9er des t\u00e2ches de synchronisation gr\u00e2ce aux boutons 'Synchroniser' partout dans l'interface web.",
+ "HeaderLibraryAccess": "Acc\u00e8s \u00e0 la librairie",
+ "HeaderChannelAccess": "Acc\u00e8s Cha\u00eene",
+ "HeaderDeviceAccess": "Acc\u00e8s \u00e0 l'appareil",
+ "HeaderSelectDevices": "S\u00e9lectionnez un appareil",
"LabelAbortedByServerShutdown": "(Annul\u00e9 par fermeture du serveur)",
"LabelScheduledTaskLastRan": "Derni\u00e8re ex\u00e9cution {0}, dur\u00e9e {1}.",
"HeaderDeleteTaskTrigger": "Supprimer le d\u00e9clencheur de t\u00e2che",
@@ -55,7 +62,7 @@
"LabelForcedStream": "(Forc\u00e9)",
"LabelDefaultForcedStream": "(Par d\u00e9faut\/Forc\u00e9)",
"LabelUnknownLanguage": "Langue inconnue",
- "ButtonMute": "Sourdine",
+ "ButtonMute": "Muet",
"ButtonUnmute": "D\u00e9sactiver sourdine",
"ButtonStop": "Arr\u00eat",
"ButtonNextTrack": "Piste suivante",
@@ -77,7 +84,7 @@
"MessageInvalidUser": "Nom d'utilisateur ou mot de passe incorrect. R\u00e9essayer.",
"HeaderLoginFailure": "\u00c9chec de la connection",
"HeaderAllRecordings": "Tous les enregistrements",
- "RecommendationBecauseYouLike": "Parce-que vous aimez {0}",
+ "RecommendationBecauseYouLike": "Parce que vous aimez {0}",
"RecommendationBecauseYouWatched": "Parce que vous avez regard\u00e9 {0}",
"RecommendationDirectedBy": "R\u00e9alis\u00e9 par {0}",
"RecommendationStarring": "Mettant en vedette {0}",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "D\u00e9placer \u00e0 droite",
"ButtonBrowseOnlineImages": "Parcourir les images en ligne",
"HeaderDeleteItem": "Supprimer l'item",
- "ConfirmDeleteItem": "\u00cates-vous s\u00fbr de vouloir supprimer cet item de votre biblioth\u00e8que ?",
+ "ConfirmDeleteItem": "Supprimer cet \u00e9l\u00e9ment l'effacera \u00e0 la fois du syst\u00e8me de fichiers et de votre biblioth\u00e8que de media. Etes-vous s\u00fbr de vouloir continuer ?",
"MessagePleaseEnterNameOrId": "Merci de saisir un nom ou un ID externe.",
"MessageValueNotCorrect": "La valeur saisie est incorrecte. Merci de r\u00e9\u00e9ssayer.",
"MessageItemSaved": "Item sauvegard\u00e9.",
@@ -396,6 +403,7 @@
"LabelYear": "Ann\u00e9e :",
"LabelDateOfBirth": "Date de naissance :",
"LabelBirthYear": "Ann\u00e9e de naissance :",
+ "LabelBirthDate": "Date de naissance :",
"LabelDeathDate": "Date de d\u00e9c\u00e8s :",
"HeaderRemoveMediaLocation": "Supprimer l'emplacement m\u00e9dia",
"MessageConfirmRemoveMediaLocation": "Etes vous sur de vouloir supprimer cet emplacement?",
@@ -409,9 +417,9 @@
"ButtonRename": "Renommer",
"ButtonChangeType": "Changer le type",
"HeaderMediaLocations": "Emplacement des M\u00e9dias",
- "LabelFolderTypeValue": "Type de r\u00e9pertoire: {0}",
+ "LabelContentTypeValue": "Type de contenu : {0}",
"LabelPathSubstitutionHelp": "Optionnel: La substitution de chemin peut rediriger les r\u00e9pertoires serveurs vers des partages r\u00e9seau afin que le client acc\u00e8de directement \u00e0 la lecture.",
- "FolderTypeMixed": "Film et TV m\u00e9lang\u00e9s",
+ "FolderTypeUnset": "Non d\u00e9fini (contenu m\u00e9lang\u00e9)",
"FolderTypeMovies": "Films",
"FolderTypeMusic": "Musique",
"FolderTypeAdultVideos": "Vid\u00e9os Adultes",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Vid\u00e9os personnelles",
"FolderTypeGames": "Jeux",
"FolderTypeBooks": "Livres",
- "FolderTypeTvShows": "S\u00e9ries TV",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Films",
"TabSeries": "S\u00e9ries",
"TabEpisodes": "\u00c9pisodes",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "et controles facilement les autres p\u00e9riph\u00e9riques et applications Medi Browser",
"MessageEnjoyYourStay": "Profitez en bien",
"DashboardTourDashboard": "Le tableau de bord du serveur vous permet de g\u00e9rer votre serveur et vos utilisateurs. Vous saurez toujours qui fait quoi et o\u00f9 ils sont.",
+ "DashboardTourHelp": "L'aide contextuelle de l'application permet d'ouvrir les pages du wiki relatives au contenu affich\u00e9.",
"DashboardTourUsers": "Cr\u00e9er facilement des comptes utilisateurs pour vos amis et votre famille, chacun avec ses propres droits, biblioth\u00e8ques accessibles, contr\u00f4le parental et plus encore.",
"DashboardTourCinemaMode": "Le mode cin\u00e9ma apporte l'exp\u00e9rience du cin\u00e9ma directement dans votre salon avec l'abilit\u00e9 de lire les bandes-annonces et les introductions personnalis\u00e9es avant le programme principal.",
"DashboardTourChapters": "Autoriser la g\u00e9n\u00e9ration des images des chapitres de vos vid\u00e9os pour une pr\u00e9sentation plus agr\u00e9able pendant le visionnage",
@@ -636,13 +645,26 @@
"MessageSyncJobCreated": "Job de synchronisation cr\u00e9\u00e9.",
"LabelSyncTo": "Synchronis\u00e9 avec:",
"LabelSyncJobName": "Nom du job de synchronisation:",
- "LabelQuality": "Quality:",
- "OptionHigh": "High",
- "OptionMedium": "Medium",
+ "LabelQuality": "Qualit\u00e9:",
+ "OptionHigh": "Haute",
+ "OptionMedium": "Moyenne",
"OptionLow": "Basse",
"HeaderSettings": "Param\u00e8tres",
"OptionAutomaticallySyncNewContent": "Synchroniser automatiquement le nouveau contenu",
- "OptionAutomaticallySyncNewContentHelp": "Le nouveau contenu ajout\u00e9 \u00e0 ces r\u00e9pertoires sera automatiquement synchronis\u00e9 \u00e0 votre p\u00e9riph\u00e9rique.",
+ "OptionAutomaticallySyncNewContentHelp": "Les nouveaux contenus ajout\u00e9s \u00e0 cette cat\u00e9gorie seront automatiquement synchronis\u00e9s avec le p\u00e9riph\u00e9rique.",
"OptionSyncUnwatchedVideosOnly": "Synchroniser seulement les vid\u00e9os non lus.",
- "OptionSyncUnwatchedVideosOnlyHelp": "Seulement les vid\u00e9os non lus seront synchronis\u00e9es et seront supprim\u00e9es du p\u00e9riph\u00e9rique au fur et \u00e0 mesure qu'elles sont lus."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Seulement les vid\u00e9os non lus seront synchronis\u00e9es et seront supprim\u00e9es du p\u00e9riph\u00e9rique au fur et \u00e0 mesure qu'elles sont lus.",
+ "LabelItemLimit": "Maximum d'\u00e9l\u00e9ments :",
+ "LabelItemLimitHelp": "Optionnel : d\u00e9finit le nombre maximum d'\u00e9l\u00e9ments qui seront synchronis\u00e9s.",
+ "MessageBookPluginRequired": "N\u00e9cessite l'installation du plugin Bookshelf",
+ "MessageGamePluginRequired": "N\u00e9cessite l'installation du plugin GameBrowser",
+ "MessageUnsetContentHelp": "Le contenu sera affich\u00e9 sous forme de r\u00e9pertoires. Pour un r\u00e9sultat optimal, utilisez le gestionnaire de m\u00e9tadonn\u00e9es pour d\u00e9finir le type de contenu des sous-r\u00e9pertoires.",
+ "SyncJobItemStatusQueued": "Mis en file d'attente",
+ "SyncJobItemStatusConverting": "Conversion en cours",
+ "SyncJobItemStatusTransferring": "Transfert en cours",
+ "SyncJobItemStatusSynced": "Synchronis\u00e9",
+ "SyncJobItemStatusFailed": "Echou\u00e9",
+ "SyncJobItemStatusRemovedFromDevice": "Supprim\u00e9 de l'appareil",
+ "SyncJobItemStatusCancelled": "Annul\u00e9",
+ "MessageJobItemHasNoActions": "D"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json
index 7d268200b..772447520 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json
@@ -40,6 +40,13 @@
"LabelStopping": "Stopping",
"LabelCancelled": "(cancelled)",
"LabelFailed": "(failed)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "\u05e9\u05de\u05d5\u05e8",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Library Access",
+ "HeaderChannelAccess": "Channel Access",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(Aborted by server shutdown)",
"LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.",
"HeaderDeleteTaskTrigger": "Delete Task Trigger",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Move right",
"ButtonBrowseOnlineImages": "Browse online images",
"HeaderDeleteItem": "Delete Item",
- "ConfirmDeleteItem": "Are you sure you wish to delete this item from your library?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "Please enter a name or an external Id.",
"MessageValueNotCorrect": "The value entered is not correct. Please try again.",
"MessageItemSaved": "Item saved.",
@@ -396,6 +403,7 @@
"LabelYear": "Year:",
"LabelDateOfBirth": "Date of birth:",
"LabelBirthYear": "Birth year:",
+ "LabelBirthDate": "Birth date:",
"LabelDeathDate": "Death date:",
"HeaderRemoveMediaLocation": "Remove Media Location",
"MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?",
@@ -409,9 +417,9 @@
"ButtonRename": "Rename",
"ButtonChangeType": "Change type",
"HeaderMediaLocations": "Media Locations",
- "LabelFolderTypeValue": "Folder type: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Optional: Path substitution can map server paths to network shares that clients can access for direct playback.",
- "FolderTypeMixed": "Mixed movies & tv",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "Movies",
"FolderTypeMusic": "Music",
"FolderTypeAdultVideos": "Adult videos",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Home videos",
"FolderTypeGames": "Games",
"FolderTypeBooks": "Books",
- "FolderTypeTvShows": "TV shows",
+ "FolderTypeTvShows": "TV",
"TabMovies": "\u05e1\u05e8\u05d8\u05d9\u05dd",
"TabSeries": "\u05e1\u05d3\u05e8\u05d5\u05ea",
"TabEpisodes": "\u05e4\u05e8\u05e7\u05d9\u05dd",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "and easily controls other devices and Media Browser apps",
"MessageEnjoyYourStay": "Enjoy your stay",
"DashboardTourDashboard": "The server dashboard allows you to monitor your server and your users. You'll always know who is doing what and where they are.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Easily create user accounts for your friends and family, each with their own permissions, library access, parental controls and more.",
"DashboardTourCinemaMode": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
"DashboardTourChapters": "Enable chapter image generation for your videos for a more pleasing presentation while viewing.",
@@ -642,7 +651,20 @@
"OptionLow": "Low",
"HeaderSettings": "Settings",
"OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
+ "OptionAutomaticallySyncNewContentHelp": "New content added to this category will be automatically synced to the device.",
"OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
- "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
+ "LabelItemLimit": "Item limit:",
+ "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+ "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+ "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/hr.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/hr.json
index ef186d800..dca6aedce 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/hr.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/hr.json
@@ -40,6 +40,13 @@
"LabelStopping": "Stopping",
"LabelCancelled": "(cancelled)",
"LabelFailed": "(failed)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "Snimi",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Library Access",
+ "HeaderChannelAccess": "Channel Access",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(Aborted by server shutdown)",
"LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.",
"HeaderDeleteTaskTrigger": "Delete Task Trigger",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Move right",
"ButtonBrowseOnlineImages": "Browse online images",
"HeaderDeleteItem": "Delete Item",
- "ConfirmDeleteItem": "Are you sure you wish to delete this item from your library?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "Please enter a name or an external Id.",
"MessageValueNotCorrect": "The value entered is not correct. Please try again.",
"MessageItemSaved": "Item saved.",
@@ -396,6 +403,7 @@
"LabelYear": "Year:",
"LabelDateOfBirth": "Date of birth:",
"LabelBirthYear": "Birth year:",
+ "LabelBirthDate": "Birth date:",
"LabelDeathDate": "Death date:",
"HeaderRemoveMediaLocation": "Remove Media Location",
"MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?",
@@ -409,9 +417,9 @@
"ButtonRename": "Rename",
"ButtonChangeType": "Change type",
"HeaderMediaLocations": "Media Locations",
- "LabelFolderTypeValue": "Folder type: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Optional: Path substitution can map server paths to network shares that clients can access for direct playback.",
- "FolderTypeMixed": "Mixed movies & tv",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "Movies",
"FolderTypeMusic": "Music",
"FolderTypeAdultVideos": "Adult videos",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Home videos",
"FolderTypeGames": "Games",
"FolderTypeBooks": "Books",
- "FolderTypeTvShows": "TV shows",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Filmovi",
"TabSeries": "Series",
"TabEpisodes": "Epizode",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "and easily controls other devices and Media Browser apps",
"MessageEnjoyYourStay": "Enjoy your stay",
"DashboardTourDashboard": "The server dashboard allows you to monitor your server and your users. You'll always know who is doing what and where they are.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Easily create user accounts for your friends and family, each with their own permissions, library access, parental controls and more.",
"DashboardTourCinemaMode": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
"DashboardTourChapters": "Enable chapter image generation for your videos for a more pleasing presentation while viewing.",
@@ -642,7 +651,20 @@
"OptionLow": "Low",
"HeaderSettings": "Settings",
"OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
+ "OptionAutomaticallySyncNewContentHelp": "New content added to this category will be automatically synced to the device.",
"OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
- "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
+ "LabelItemLimit": "Item limit:",
+ "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+ "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+ "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json
index 8e324b335..c8823b50c 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json
@@ -40,6 +40,13 @@
"LabelStopping": "Sto fermando",
"LabelCancelled": "(cancellato)",
"LabelFailed": "(fallito)",
+ "ButtonHelp": "Aiuto",
+ "ButtonSave": "Salva",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Accesso libreria",
+ "HeaderChannelAccess": "Accesso canali",
+ "HeaderDeviceAccess": "Accesso dispositivo",
+ "HeaderSelectDevices": "Seleziona periferiche",
"LabelAbortedByServerShutdown": "(Interrotto dalla chiusura del server)",
"LabelScheduledTaskLastRan": "Ultima esecuzione {0}, taking {1}.",
"HeaderDeleteTaskTrigger": "Elimina Operazione pianificata",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Muovi a destra",
"ButtonBrowseOnlineImages": "Sfoglia le immagini Online",
"HeaderDeleteItem": "Elimina elemento",
- "ConfirmDeleteItem": "Sei sicuro di voler eliminare questo elemento dalla tua libreria?",
+ "ConfirmDeleteItem": "L'eliminazione di questo articolo sar\u00e0 eliminarlo sia dal file system e la vostra libreria multimediale. Sei sicuro di voler continuare?",
"MessagePleaseEnterNameOrId": "Inserisci il nome o id esterno.",
"MessageValueNotCorrect": "Il valore inserito non \u00e8 corretto.Riprova di nuovo.",
"MessageItemSaved": "Elemento salvato.",
@@ -396,6 +403,7 @@
"LabelYear": "Anno:",
"LabelDateOfBirth": "Data nascita:",
"LabelBirthYear": "Anno nascita:",
+ "LabelBirthDate": "Data nascita:",
"LabelDeathDate": "Anno morte:",
"HeaderRemoveMediaLocation": "Rimuovi percorso media",
"MessageConfirmRemoveMediaLocation": "Sei sicuro di voler rimuovere questa posizione?",
@@ -409,9 +417,9 @@
"ButtonRename": "Rinomina",
"ButtonChangeType": "Cambia tipo",
"HeaderMediaLocations": "Posizioni Media",
- "LabelFolderTypeValue": "Tipo cartella: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Opzionale: cambio Path pu\u00f2 mappare i percorsi del server a condivisioni di rete che i clienti possono accedere per la riproduzione diretta.",
- "FolderTypeMixed": "Misto Film & Tv",
+ "FolderTypeUnset": "Disinserito (contenuto misto)",
"FolderTypeMovies": "Film",
"FolderTypeMusic": "Musica",
"FolderTypeAdultVideos": "Video per adulti",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Video personali",
"FolderTypeGames": "Giochi",
"FolderTypeBooks": "Libri",
- "FolderTypeTvShows": "Serie TV",
+ "FolderTypeTvShows": "Tv",
"TabMovies": "Film",
"TabSeries": "Serie TV",
"TabEpisodes": "Episodi",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "e controlla facilmente altri dispositivi e applicazioni Media Browser",
"MessageEnjoyYourStay": "Godetevi il vostro soggiorno",
"DashboardTourDashboard": "Il pannello di controllo del server consente di monitorare il vostro server e gli utenti. Potrai sempre sapere chi sta facendo cosa e dove sono.",
+ "DashboardTourHelp": "In-app help offre pulsanti facili da aprire le pagine wiki relative al contenuto sullo schermo.",
"DashboardTourUsers": "Facile creazione di account utente per i vostri amici e la famiglia, ognuno con le proprie autorizzazioni, accesso alla libreria, controlli parentali e altro ancora.",
"DashboardTourCinemaMode": "Modalit\u00e0 Cinema porta l'esperienza del teatro direttamente nel tuo salotto con la possibilit\u00e0 di giocare trailer e intro personalizzati prima la caratteristica principale.",
"DashboardTourChapters": "Abilita capitolo generazione di immagini per i vostri video per una presentazione pi\u00f9 gradevole durante la visualizzazione.",
@@ -610,7 +619,7 @@
"MessageInvitationSentToUser": "Una e-mail \u00e8 stata inviata a {0}, invitandoli ad accettare l'invito di condivisione.",
"MessageInvitationSentToNewUser": "Una e-mail \u00e8 stata inviata a {0} invitandoli a firmare con Media Browser.",
"HeaderConnectionFailure": "Errore di connessione",
- "MessageUnableToConnectToServer": "We're unable to connect to the selected server right now. Please try again later.",
+ "MessageUnableToConnectToServer": "Non siamo in grado di connettersi al server selezionato al momento. Si prega di riprovare pi\u00f9 tardi.",
"ButtonSelectServer": "Selezionare il server",
"MessagePluginConfigurationRequiresLocalAccess": "Per configurare questo plugin si prega di accedere al proprio server locale direttamente.",
"MessageLoggedOutParentalControl": "L'accesso \u00e8 attualmente limitato. Si prega di riprovare pi\u00f9 tardi.",
@@ -628,21 +637,34 @@
"ButtonLinkMyMediaBrowserAccount": "Collega il mio account",
"MessageConnectAccountRequiredToInviteGuest": "Per invitare gli ospiti \u00e8 necessario collegare prima il tuo account browser media a questo server.",
"ButtonSync": "Sinc.",
- "SyncMedia": "Sync Media",
+ "SyncMedia": "Sync media",
"HeaderCancelSyncJob": "Cancel Sync",
- "CancelSyncJobConfirmation": "Are you sure you wish to cancel this sync job?",
+ "CancelSyncJobConfirmation": "Sei sicuro di voler annullare questo lavoro di sincronizzazione?",
"TabSync": "Sinc",
- "MessagePleaseSelectDeviceToSyncTo": "Please select a device to sync to.",
+ "MessagePleaseSelectDeviceToSyncTo": "Selezionare un dispositivo per la sincronizzazione",
"MessageSyncJobCreated": "Sync job created.",
"LabelSyncTo": "Sync to:",
"LabelSyncJobName": "Sync job name:",
- "LabelQuality": "Quality:",
- "OptionHigh": "High",
- "OptionMedium": "Medium",
- "OptionLow": "Low",
- "HeaderSettings": "Settings",
- "OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
- "OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
- "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched."
+ "LabelQuality": "Qualit\u00e0:",
+ "OptionHigh": "Alto",
+ "OptionMedium": "Medio",
+ "OptionLow": "Basso",
+ "HeaderSettings": "Configurazione",
+ "OptionAutomaticallySyncNewContent": "Sincronizza automaticamente nuovi contenuti",
+ "OptionAutomaticallySyncNewContentHelp": "Nuovi contenuti aggiunti a questa categoria viene sincronizzata automaticamente al dispositivo.",
+ "OptionSyncUnwatchedVideosOnly": "Sincronizza solo i video non visti",
+ "OptionSyncUnwatchedVideosOnlyHelp": "Solo i video non visti saranno sincronizzati, e video saranno rimossi dal dispositivo in cui sono guardato.",
+ "LabelItemLimit": "limite elementi:",
+ "LabelItemLimitHelp": "Opzionale. Impostare un limite al numero di elementi che verranno sincronizzati.",
+ "MessageBookPluginRequired": "Richiede l'installazione del plugin Bookshelf",
+ "MessageGamePluginRequired": "Richiede l'installazione del plugin GameBrowser",
+ "MessageUnsetContentHelp": "Il contenuto verr\u00e0 visualizzato come pianura cartelle. Per ottenere i migliori risultati utilizzare il gestore di metadati per impostare i tipi di contenuto di sottocartelle.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json
index b90370246..aff630846 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json
@@ -40,6 +40,13 @@
"LabelStopping": "Stopping",
"LabelCancelled": "(cancelled)",
"LabelFailed": "(failed)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "Save",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Library Access",
+ "HeaderChannelAccess": "Channel Access",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(Aborted by server shutdown)",
"LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.",
"HeaderDeleteTaskTrigger": "Delete Task Trigger",
@@ -251,7 +258,7 @@
"ButtonMoveRight": "Move right",
"ButtonBrowseOnlineImages": "Browse online images",
"HeaderDeleteItem": "Delete Item",
- "ConfirmDeleteItem": "Are you sure you wish to delete this item from your library?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "Please enter a name or an external Id.",
"MessageValueNotCorrect": "The value entered is not correct. Please try again.",
"MessageItemSaved": "Item saved.",
@@ -287,7 +294,7 @@
"LabelNewProgram": "NEW",
"LabelPremiereProgram": "PREMIERE",
"LabelHDProgram": "HD",
- "HeaderChangeFolderType": "Change Folder Type",
+ "HeaderChangeFolderType": "Change Content Type",
"HeaderChangeFolderTypeHelp": "To change the type, please remove and rebuild the folder with the new type.",
"HeaderAlert": "Alert",
"MessagePleaseRestart": "Please restart to finish updating.",
@@ -402,6 +409,7 @@
"LabelYear": "Year:",
"LabelDateOfBirth": "Date of birth:",
"LabelBirthYear": "Birth year:",
+ "LabelBirthDate": "Birth date:",
"LabelDeathDate": "Death date:",
"HeaderRemoveMediaLocation": "Remove Media Location",
"MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?",
@@ -416,9 +424,9 @@
"ButtonChangeType": "Change type",
"ButtonRemove": "Remove",
"HeaderMediaLocations": "Media Locations",
- "LabelFolderTypeValue": "Folder type: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Optional: Path substitution can map server paths to network shares that clients can access for direct playback.",
- "FolderTypeMixed": "Mixed movies & tv",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "Movies",
"FolderTypeMusic": "Music",
"FolderTypeAdultVideos": "Adult videos",
@@ -427,7 +435,7 @@
"FolderTypeHomeVideos": "Home videos",
"FolderTypeGames": "Games",
"FolderTypeBooks": "Books",
- "FolderTypeTvShows": "TV shows",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Movies",
"TabSeries": "Series",
"TabEpisodes": "Episodes",
@@ -595,6 +603,7 @@
"WebClientTourMobile2": "and easily controls other devices and Media Browser apps",
"MessageEnjoyYourStay": "Enjoy your stay",
"DashboardTourDashboard": "The server dashboard allows you to monitor your server and your users. You'll always know who is doing what and where they are.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Easily create user accounts for your friends and family, each with their own permissions, library access, parental controls and more.",
"DashboardTourCinemaMode": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
"DashboardTourChapters": "Enable chapter image generation for your videos for a more pleasing presentation while viewing.",
@@ -618,7 +627,7 @@
"MessageInvitationSentToUser": "An email has been sent to {0}, inviting them to accept your sharing invitation.",
"MessageInvitationSentToNewUser": "An email has been sent to {0} inviting them to sign up with Media Browser.",
"HeaderConnectionFailure": "Connection Failure",
- "MessageUnableToConnectToServer": "We're unable to connect to the selected server right now. Please try again later.",
+ "MessageUnableToConnectToServer": "We're unable to connect to the selected server right now. Please ensure it is running and try again.",
"ButtonSelectServer": "Select server",
"MessagePluginConfigurationRequiresLocalAccess": "To configure this plugin please sign in to your local server directly.",
"MessageLoggedOutParentalControl": "Access is currently restricted. Please try again later.",
@@ -650,9 +659,20 @@
"OptionLow": "Low",
"HeaderSettings": "Settings",
"OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
+ "OptionAutomaticallySyncNewContentHelp": "New content added to this category will be automatically synced to the device.",
"OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
"OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
"LabelItemLimit": "Item limit:",
- "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced."
+ "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+ "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+ "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
}
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json
index 85cd90d11..11b194fd0 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json
@@ -40,6 +40,13 @@
"LabelStopping": "\u0422\u043e\u049b\u0442\u0430\u0442\u044b\u043b\u0443\u0434\u0430",
"LabelCancelled": "(\u0431\u043e\u043b\u0434\u044b\u0440\u044b\u043b\u043c\u0430\u0434\u044b)",
"LabelFailed": "(\u0441\u04d9\u0442\u0441\u0456\u0437)",
+ "ButtonHelp": "\u0410\u043d\u044b\u049b\u0442\u0430\u043c\u0430",
+ "ButtonSave": "\u0421\u0430\u049b\u0442\u0430\u0443",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "\u0422\u0430\u0441\u0443\u0448\u044b\u0445\u0430\u043d\u0430\u0493\u0430 \u049b\u0430\u0442\u044b\u043d\u0430\u0441\u0443",
+ "HeaderChannelAccess": "\u0410\u0440\u043d\u0430\u0493\u0430 \u049b\u0430\u0442\u044b\u043d\u0430\u0441\u0443",
+ "HeaderDeviceAccess": "\u049a\u04b1\u0440\u044b\u043b\u0493\u044b\u0493\u0430 \u049b\u0430\u0442\u044b\u043d\u0430\u0441",
+ "HeaderSelectDevices": "\u049a\u04b1\u0440\u044b\u043b\u0493\u044b\u043b\u0430\u0440\u0434\u044b \u0442\u0430\u04a3\u0434\u0430\u0443",
"LabelAbortedByServerShutdown": "(\u0421\u0435\u0440\u0432\u0435\u0440 \u0436\u04b1\u043c\u044b\u0441\u044b \u0430\u044f\u049b\u0442\u0430\u043b\u0443\u044b\u043d\u0430 \u0431\u0430\u0439\u043b\u0430\u043d\u044b\u0441\u0442\u044b \u04af\u0437\u0456\u043b\u0434\u0456)",
"LabelScheduledTaskLastRan": "\u0421\u043e\u04a3\u0493\u044b \u0456\u0441\u043a\u0435 \u049b\u043e\u0441\u044b\u043b\u0493\u0430\u043d\u044b {0}, {1} \u0443\u0430\u049b\u044b\u0442 \u0430\u043b\u0434\u044b.",
"HeaderDeleteTaskTrigger": "\u0422\u0430\u043f\u0441\u044b\u0440\u043c\u0430 \u0442\u0440\u0438\u0433\u0433\u0435\u0440\u0456\u043d \u0436\u043e\u044e",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "\u041e\u04a3\u0493\u0430 \u0436\u044b\u043b\u0436\u044b\u0442\u0443",
"ButtonBrowseOnlineImages": "\u0416\u0435\u043b\u0456\u0434\u0435\u0433\u0456 \u0441\u0443\u0440\u0435\u0442\u0442\u0435\u0440\u0434\u0456 \u0448\u043e\u043b\u0443",
"HeaderDeleteItem": "\u042d\u043b\u0435\u043c\u0435\u043d\u0442\u0442\u0456 \u0436\u043e\u044e",
- "ConfirmDeleteItem": "\u0428\u044b\u043d\u044b\u043c\u0435\u043d \u043e\u0441\u044b \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0442\u0456 \u0442\u0430\u0441\u0443\u0448\u044b\u0445\u0430\u043d\u0430\u0434\u0430\u043d \u0436\u043e\u044e \u049b\u0430\u0436\u0435\u0442 \u043f\u0435?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "\u0410\u0442\u044b\u043d \u043d\u0435\u043c\u0435\u0441\u0435 \u0441\u044b\u0440\u0442\u049b\u044b ID \u0435\u043d\u0433\u0456\u0437\u0456\u04a3\u0456\u0437.",
"MessageValueNotCorrect": "\u0415\u043d\u0433\u0456\u0437\u0456\u043b\u0433\u0435\u043d \u043c\u04d9\u043d \u0434\u04b1\u0440\u044b\u0441 \u0435\u043c\u0435\u0441. \u04d8\u0440\u0435\u043a\u0435\u0442\u0442\u0456 \u049b\u0430\u0439\u0442\u0430\u043b\u0430\u04a3\u044b\u0437.",
"MessageItemSaved": "\u042d\u043b\u0435\u043c\u0435\u043d\u0442 \u0441\u0430\u049b\u0442\u0430\u043b\u0434\u044b.",
@@ -396,6 +403,7 @@
"LabelYear": "\u0416\u044b\u043b\u044b:",
"LabelDateOfBirth": "\u0422\u0443\u0493\u0430\u043d \u043a\u04af\u043d\u0456:",
"LabelBirthYear": "\u0422\u0443\u0493\u0430\u043d \u0436\u044b\u043b\u044b:",
+ "LabelBirthDate": "\u0422\u0443\u0493\u0430\u043d \u043a\u04af\u043d\u0456:",
"LabelDeathDate": "\u04e8\u043b\u0433\u0435\u043d \u043a\u04af\u043d\u0456:",
"HeaderRemoveMediaLocation": "\u0422\u0430\u0441\u0443\u0448\u044b\u0434\u0435\u0440\u0435\u043a\u0442\u0435\u0440 \u043e\u0440\u043d\u0430\u043b\u0430\u0441\u0443\u044b\u043d \u0430\u043b\u0430\u0441\u0442\u0430\u0443",
"MessageConfirmRemoveMediaLocation": "\u0428\u044b\u043d\u044b\u043c\u0435\u043d \u043e\u0441\u044b \u043e\u0440\u043d\u0430\u043b\u0430\u0441\u0443\u0434\u044b \u0430\u043b\u0430\u0441\u0442\u0430\u0443 \u049b\u0430\u0436\u0435\u0442 \u043f\u0435?",
@@ -409,9 +417,9 @@
"ButtonRename": "\u049a\u0430\u0439\u0442\u0430 \u0430\u0442\u0430\u0443",
"ButtonChangeType": "\u0422\u04af\u0440\u0456\u043d \u04e9\u0437\u0433\u0435\u0440\u0442\u0443",
"HeaderMediaLocations": "\u0422\u0430\u0441\u0443\u0448\u044b \u0434\u0435\u0440\u0435\u043a\u0442\u0435\u0440 \u043e\u0440\u043d\u0430\u043b\u0430\u0441\u0443\u043b\u0430\u0440\u044b",
- "LabelFolderTypeValue": "\u049a\u0430\u043b\u0442\u0430 \u0442\u04af\u0440\u0456: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "\u049a\u0430\u043b\u0430\u0443 \u0431\u043e\u0439\u044b\u043d\u0448\u0430: \u0416\u043e\u043b \u0430\u043b\u043c\u0430\u0441\u0442\u044b\u0440\u0443 \u0430\u0440\u049b\u044b\u043b\u044b \u0441\u0435\u0440\u0432\u0435\u0440\u0434\u0435\u0433\u0456 \u0436\u043e\u043b\u0434\u0430\u0440\u0434\u044b \u0442\u0456\u043a\u0435\u043b\u0435\u0439 \u043e\u0439\u043d\u0430\u0442\u0443 \u04af\u0448\u0456\u043d \u043a\u043b\u0438\u0435\u043d\u0442\u0442\u0435\u0440 \u049b\u0430\u0442\u044b\u043d\u0430\u0441\u0430 \u0430\u043b\u0430\u0442\u044b\u043d \u0436\u0435\u043b\u0456\u043b\u0456\u043a \u049b\u043e\u0440 \u043a\u04e9\u0437\u0434\u0435\u0440\u0456\u043c\u0435\u043d \u0431\u0430\u0439\u043b\u0430\u043d\u044b\u0441\u0442\u044b\u0440\u0443\u044b \u043c\u04af\u043c\u043a\u0456\u043d.",
- "FolderTypeMixed": "\u0410\u0440\u0430\u043b\u0430\u0441 (\u043a\u0438\u043d\u043e \u0436\u04d9\u043d\u0435 \u0442\u0434)",
+ "FolderTypeUnset": "\u0422\u0430\u0493\u0430\u0439\u044b\u043d\u0434\u0430\u043b\u043c\u0430\u0493\u0430\u043d (\u04d9\u0440\u0442\u04af\u0440\u043b\u0456 \u043c\u0430\u0437\u043c\u04b1\u043d)",
"FolderTypeMovies": "\u0424\u0438\u043b\u044c\u043c\u0434\u0435\u0440",
"FolderTypeMusic": "\u041c\u0443\u0437\u044b\u043a\u0430",
"FolderTypeAdultVideos": "\u0415\u0440\u0435\u0441\u0435\u043a\u0442\u0456\u043a \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0440",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "\u04ae\u0439 \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0440\u0456",
"FolderTypeGames": "\u041e\u0439\u044b\u043d\u0434\u0430\u0440",
"FolderTypeBooks": "\u041a\u0456\u0442\u0430\u043f\u0442\u0430\u0440",
- "FolderTypeTvShows": "\u0422\u0414 \u043a\u04e9\u0440\u0441\u0435\u0442\u0456\u043c\u0434\u0435\u0440\u0456",
+ "FolderTypeTvShows": "\u0422\u0414",
"TabMovies": "\u0424\u0438\u043b\u044c\u043c\u0434\u0435\u0440",
"TabSeries": "\u0421\u0435\u0440\u0438\u0430\u043b",
"TabEpisodes": "\u042d\u043f\u0438\u0437\u043e\u0434\u0442\u0430\u0440",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "\u0441\u043e\u043d\u044b\u043c\u0435\u043d \u049b\u0430\u0442\u0430\u0440 \u0431\u0430\u0441\u049b\u0430 \u049b\u04b1\u0440\u044b\u043b\u0493\u044b\u043b\u0430\u0440\u0434\u044b \u0436\u04d9\u043d\u0435 Media Browser \u049b\u043e\u043b\u0434\u0430\u043d\u0431\u0430\u043b\u0430\u0440\u044b\u043d \u0431\u0430\u0441\u049b\u0430\u0440\u0430\u0434\u044b",
"MessageEnjoyYourStay": "\u0416\u0430\u0493\u044b\u043c\u0434\u044b \u0435\u0440\u043c\u0435\u043a \u0435\u0442\u0456\u04a3\u0456\u0437",
"DashboardTourDashboard": "\u0421\u0435\u0440\u0432\u0435\u0440\u0434\u0456\u04a3 \u0431\u0430\u049b\u044b\u043b\u0430\u0443 \u0442\u0430\u049b\u0442\u0430\u0441\u044b \u0441\u0435\u0440\u0432\u0435\u0440\u0434\u0456 \u0436\u04d9\u043d\u0435 \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b\u043b\u0430\u0440\u0434\u044b \u0431\u0430\u049b\u044b\u043b\u0430\u0443\u044b \u043c\u04af\u043c\u043a\u0456\u043d\u0434\u0456\u0433\u0456\u043d\u0435 \u0440\u04b1\u049b\u0441\u0430\u0442 \u0435\u0442\u0435\u0434\u0456. \u041a\u0456\u043c \u043d\u0435 \u0456\u0441\u0442\u0435\u0433\u0435\u043d\u0456\u043d \u0436\u04d9\u043d\u0435 \u049b\u0430\u0439\u0434\u0430 \u0442\u04b1\u0440\u0493\u0430\u043d\u044b\u043d \u0431\u0456\u043b\u0456\u043f \u0436\u0430\u0442\u0430\u0441\u044b\u0437.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "\u0414\u043e\u0441\u0442\u0430\u0440\u044b\u04a3\u044b\u0437 \u0431\u0435\u043d \u043e\u0442\u0431\u0430\u0441\u044b\u04a3\u044b\u0437 \u04d9\u0440\u049b\u0430\u0439\u0441\u044b\u043d\u0430 \u04e9\u0437\u0456\u043d\u0456\u04a3 \u049b\u04b1\u049b\u044b\u049b\u0442\u0430\u0440\u044b, \u0442\u0430\u0441\u0443\u0448\u044b\u0445\u0430\u043d\u0430 \u049b\u0430\u0442\u044b\u043d\u0430\u0441\u0443\u044b, \u043c\u0430\u0437\u043c\u04b1\u043d \u0431\u0430\u0441\u049b\u0430\u0440\u0443\u044b \u0436\u04d9\u043d\u0435 \u0442.\u0431. \u0431\u0430\u0440 \u0442\u0456\u0440\u043a\u0435\u043b\u0433\u0456\u043b\u0435\u0440\u0456\u043d \u0436\u0435\u04a3\u0456\u043b \u0436\u0430\u0441\u0430\u04a3\u044b\u0437.",
"DashboardTourCinemaMode": "\u041a\u0438\u043d\u043e\u0442\u0435\u0430\u0442\u0440 \u0440\u0435\u0436\u0456\u043c\u0456 \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043b\u0435\u0440\u0434\u0456 \u0436\u04d9\u043d\u0435 \u0442\u0435\u04a3\u0448\u0435\u043b\u0433\u0435\u043d \u043a\u04e9\u0440\u043d\u0435\u0443\u0434\u0456 \u0431\u0430\u0441\u0442\u044b \u049b\u0430\u0441\u0438\u0435\u0442\u0442\u0456\u04a3 \u0430\u043b\u0434\u044b\u043d\u0434\u0430 \u043e\u0439\u043d\u0430\u0442\u0443 \u049b\u0430\u0431\u0456\u043b\u0435\u0442\u0456\u043c\u0435\u043d \u043a\u0438\u043d\u043e \u043a\u04e9\u0440\u0441\u0435\u0442\u0435\u0442\u0456\u043d \u0437\u0430\u043b \u04d9\u0441\u0435\u0440\u0456\u043d \u049b\u043e\u043d\u0430\u049b\u0436\u0430\u0439\u044b\u04a3\u044b\u0437\u0493\u0430 \u0442\u0456\u043a\u0435\u043b\u0435\u0439 \u0436\u0435\u0442\u043a\u0456\u0437\u0435\u0434\u0456.",
"DashboardTourChapters": "\u0411\u0435\u0439\u043d\u0435\u043b\u0435\u0440\u0434\u0456 \u049b\u0430\u0440\u0430\u0493\u0430\u043d \u043a\u0435\u0437\u0434\u0435 \u04b1\u043d\u0430\u043c\u0434\u044b\u043b\u0430\u0443 \u043a\u04e9\u0440\u0441\u0435\u0442\u0456\u043c \u04af\u0448\u0456\u043d \u0441\u0430\u0445\u043d\u0430 \u0441\u0443\u0440\u0435\u0442\u0442\u0435\u0440\u0456\u043d \u0442\u0443\u0434\u044b\u0440\u0443\u044b\u043d \u049b\u043e\u0441\u044b\u04a3\u044b\u0437.",
@@ -642,7 +651,20 @@
"OptionLow": "\u0422\u04e9\u043c\u0435\u043d",
"HeaderSettings": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043b\u0435\u0440",
"OptionAutomaticallySyncNewContent": "\u0416\u0430\u04a3\u0430 \u043c\u0430\u0437\u043c\u04b1\u043d\u0434\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0442\u044b \u0442\u04af\u0440\u0434\u0435 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0434\u0430\u0443",
- "OptionAutomaticallySyncNewContentHelp": "\u041e\u0441\u044b \u049b\u0430\u043b\u0442\u0430\u043b\u0430\u0440\u0493\u0430 \u04af\u0441\u0442\u0435\u043b\u0456\u043d\u0433\u0435\u043d \u0436\u0430\u04a3\u0430 \u043c\u0430\u0437\u043c\u04b1\u043d \u049b\u04b1\u0440\u044b\u043b\u0493\u044b\u043c\u0435\u043d \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0442\u044b \u0442\u04af\u0440\u0434\u0435 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0434\u0430\u043b\u0430\u0434\u044b.",
+ "OptionAutomaticallySyncNewContentHelp": "\u041e\u0441\u044b \u0441\u0430\u043d\u0430\u0442\u049b\u0430 \u049b\u043e\u0441\u044b\u043b\u0493\u0430\u043d \u0436\u0430\u04a3\u0430 \u043c\u0430\u0437\u043c\u04b1\u043d \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0442\u044b \u0442\u04af\u0440\u0434\u0435 \u049b\u04b1\u0440\u044b\u043b\u0493\u044b\u043c\u0435\u043d \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0434\u0430\u043b\u044b\u043d\u0430\u0434\u044b.",
"OptionSyncUnwatchedVideosOnly": "\u0422\u0435\u043a \u049b\u0430\u043d\u0430 \u043a\u04e9\u0440\u0456\u043b\u043c\u0435\u0433\u0435\u043d \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0440\u0434\u0456 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0434\u0430\u0443",
- "OptionSyncUnwatchedVideosOnlyHelp": "\u0422\u0435\u043a \u049b\u0430\u043d\u0430 \u049b\u0430\u0440\u0430\u0440\u043c\u0430\u0493\u0430\u043d \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0440 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0434\u0430\u043b\u0430\u0434\u044b, \u0436\u04d9\u043d\u0435 \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0440 \u049b\u0430\u0440\u0430\u043b\u0493\u0430\u043d\u043d\u0430\u043d \u043a\u0435\u0439\u0456\u043d \u049b\u04b1\u0440\u044b\u043b\u0493\u044b\u0434\u0430\u043d \u0430\u043b\u0430\u0441\u0442\u0430\u043b\u0430\u0434\u044b."
+ "OptionSyncUnwatchedVideosOnlyHelp": "\u0422\u0435\u043a \u049b\u0430\u043d\u0430 \u049b\u0430\u0440\u0430\u0440\u043c\u0430\u0493\u0430\u043d \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0440 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0434\u0430\u043b\u044b\u043d\u0430\u0434\u044b, \u0436\u04d9\u043d\u0435 \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0440 \u049b\u0430\u0440\u0430\u043b\u0493\u0430\u043d\u043d\u0430\u043d \u043a\u0435\u0439\u0456\u043d \u049b\u04b1\u0440\u044b\u043b\u0493\u044b\u0434\u0430\u043d \u0430\u043b\u0430\u0441\u0442\u0430\u043b\u0430\u0434\u044b.",
+ "LabelItemLimit": "\u042d\u043b\u0435\u043c\u0435\u043d\u0442\u0442\u0435\u0440 \u0448\u0435\u0433\u0456:",
+ "LabelItemLimitHelp": "\u049a\u0430\u043b\u0430\u0443 \u0431\u043e\u0439\u044b\u043d\u0448\u0430: \u0421\u0438\u043d\u0445\u0440\u043e\u043d\u0434\u0430\u043b\u0430\u0442\u044b\u043d \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0442\u0435\u0440 \u0441\u0430\u043d\u044b \u04af\u0448\u0456\u043d \u0448\u0435\u043a\u0442\u0456 \u043e\u0440\u043d\u0430\u0442\u044b\u04a3\u044b\u0437.",
+ "MessageBookPluginRequired": "Bookshelf \u043f\u043b\u0430\u0433\u0438\u043d\u0456\u043d \u043e\u0440\u043d\u0430\u0442\u0443\u0434\u044b \u049b\u0430\u0436\u0435\u0442 \u0435\u0442\u0435\u0434\u0456",
+ "MessageGamePluginRequired": "GameBrowser \u043f\u043b\u0430\u0433\u0438\u043d\u0456\u043d \u043e\u0440\u043d\u0430\u0442\u0443\u0434\u044b \u049b\u0430\u0436\u0435\u0442 \u0435\u0442\u0435\u0434\u0456",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json
index 89aefed9d..8c0a9efe7 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json
@@ -40,6 +40,13 @@
"LabelStopping": "Stopping",
"LabelCancelled": "(cancelled)",
"LabelFailed": "(failed)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "Save",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Library Access",
+ "HeaderChannelAccess": "Channel Access",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(Aborted by server shutdown)",
"LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.",
"HeaderDeleteTaskTrigger": "Delete Task Trigger",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Move right",
"ButtonBrowseOnlineImages": "Browse online images",
"HeaderDeleteItem": "Delete Item",
- "ConfirmDeleteItem": "Are you sure you wish to delete this item from your library?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "Please enter a name or an external Id.",
"MessageValueNotCorrect": "The value entered is not correct. Please try again.",
"MessageItemSaved": "Item saved.",
@@ -396,6 +403,7 @@
"LabelYear": "Year:",
"LabelDateOfBirth": "Date of birth:",
"LabelBirthYear": "Birth year:",
+ "LabelBirthDate": "Birth date:",
"LabelDeathDate": "Death date:",
"HeaderRemoveMediaLocation": "Remove Media Location",
"MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?",
@@ -409,9 +417,9 @@
"ButtonRename": "Rename",
"ButtonChangeType": "Change type",
"HeaderMediaLocations": "Media Locations",
- "LabelFolderTypeValue": "Folder type: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Optional: Path substitution can map server paths to network shares that clients can access for direct playback.",
- "FolderTypeMixed": "Mixed movies & tv",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "Movies",
"FolderTypeMusic": "Music",
"FolderTypeAdultVideos": "Adult videos",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Home videos",
"FolderTypeGames": "Games",
"FolderTypeBooks": "Books",
- "FolderTypeTvShows": "TV shows",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Movies",
"TabSeries": "Series",
"TabEpisodes": "Episodes",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "and easily controls other devices and Media Browser apps",
"MessageEnjoyYourStay": "Enjoy your stay",
"DashboardTourDashboard": "The server dashboard allows you to monitor your server and your users. You'll always know who is doing what and where they are.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Easily create user accounts for your friends and family, each with their own permissions, library access, parental controls and more.",
"DashboardTourCinemaMode": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
"DashboardTourChapters": "Enable chapter image generation for your videos for a more pleasing presentation while viewing.",
@@ -642,7 +651,20 @@
"OptionLow": "Low",
"HeaderSettings": "Settings",
"OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
+ "OptionAutomaticallySyncNewContentHelp": "New content added to this category will be automatically synced to the device.",
"OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
- "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
+ "LabelItemLimit": "Item limit:",
+ "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+ "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+ "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json
index a5fd9bb9b..14c46db74 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json
@@ -40,6 +40,13 @@
"LabelStopping": "Stoppe",
"LabelCancelled": "(kansellert)",
"LabelFailed": "(Feilet)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "lagre",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Bibliotek tilgang",
+ "HeaderChannelAccess": "Kanal tilgang",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(Avbrutt av server shutdown)",
"LabelScheduledTaskLastRan": "Sist kj\u00f8rt {0}, tar {1}.",
"HeaderDeleteTaskTrigger": "Slett Oppgave Trigger",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Flytt til h\u00f8yre",
"ButtonBrowseOnlineImages": "Bla igjennom bilder online",
"HeaderDeleteItem": "Slett element",
- "ConfirmDeleteItem": "Er du sikker p\u00e5 at du vil slette dette elementet fra ditt bibliotek?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "Vennligst skriv ett navn eller en ekstern id.",
"MessageValueNotCorrect": "Verdien som ble skrevet er ikke korrekt. Vennligst pr\u00f8v igjen.",
"MessageItemSaved": "Element lagret.",
@@ -396,6 +403,7 @@
"LabelYear": "\u00c5r:",
"LabelDateOfBirth": "F\u00f8dseldato:",
"LabelBirthYear": "F\u00f8dsels\u00e5r:",
+ "LabelBirthDate": "Birth date:",
"LabelDeathDate": "D\u00f8ds dato:",
"HeaderRemoveMediaLocation": "Fjern Media Mappe",
"MessageConfirmRemoveMediaLocation": "Er du sikker p\u00e5 at du vil slette dette stedet??",
@@ -409,9 +417,9 @@
"ButtonRename": "Gi nytt navn",
"ButtonChangeType": "Endre type",
"HeaderMediaLocations": "Media Steder",
- "LabelFolderTypeValue": "Mappe type: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Valgfritt: Sti erstatter kan koble server stier til nettverkressurser som klienter har tilgang til for direkte avspilling.",
- "FolderTypeMixed": "Mikset filmer & tv",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "Filmer",
"FolderTypeMusic": "Musikk",
"FolderTypeAdultVideos": "Voksen videoer",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Hjemme videoer",
"FolderTypeGames": "Spill",
"FolderTypeBooks": "B\u00f8ker",
- "FolderTypeTvShows": "TV program",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Filmer",
"TabSeries": "Serier",
"TabEpisodes": "Episoder",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "og enkelt styrer andre enheter og mediabrowser apps",
"MessageEnjoyYourStay": "Nyt oppholdet",
"DashboardTourDashboard": "The server dashboard allows you to monitor your server and your users. You'll always know who is doing what and where they are.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Opprett bruker kontoer enkelt for dine venner og familie, hver med deres egne rettigheter, bibliotek tillgang, foreldre kontroll og mere til.",
"DashboardTourCinemaMode": "Kino-modus bringer kinoopplevelsen direkte til din stue med muligheten til \u00e5 spille trailere og tilpassede introer f\u00f8r filmen begynner.",
"DashboardTourChapters": "Aktiver generering av kapittel bilder for dine videoer for en mer behagelig presentasjon mens du ser p\u00e5.",
@@ -642,7 +651,20 @@
"OptionLow": "Low",
"HeaderSettings": "Settings",
"OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
+ "OptionAutomaticallySyncNewContentHelp": "New content added to this category will be automatically synced to the device.",
"OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
- "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
+ "LabelItemLimit": "Item limit:",
+ "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+ "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+ "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json
index 53445c370..fec025f91 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json
@@ -40,6 +40,13 @@
"LabelStopping": "Stoppen",
"LabelCancelled": "(Geannuleerd)",
"LabelFailed": "(mislukt)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "Opslaan",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Bibliotheek toegang",
+ "HeaderChannelAccess": "Kanaal toegang",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(Afgebroken door afsluiten van de server)",
"LabelScheduledTaskLastRan": "Laatste keer {0}, duur {1}.",
"HeaderDeleteTaskTrigger": "Verwijderen Taak Trigger",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Verplaats naar rechts",
"ButtonBrowseOnlineImages": "Blader door online afbeeldingen",
"HeaderDeleteItem": "Item verwijderen",
- "ConfirmDeleteItem": "Weet u zeker dat u dit item uit uw bibliotheek wilt verwijderen?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "Voer een naam of een externe Id in",
"MessageValueNotCorrect": "De ingevoerde waarde is niet correct. Probeer het opnieuw.",
"MessageItemSaved": "Item opgeslagen.",
@@ -396,6 +403,7 @@
"LabelYear": "Jaar:",
"LabelDateOfBirth": "Geboortedatum:",
"LabelBirthYear": "Geboorte jaar:",
+ "LabelBirthDate": "Geboortedatum:",
"LabelDeathDate": "Overlijdens datum:",
"HeaderRemoveMediaLocation": "Verwijder media locatie",
"MessageConfirmRemoveMediaLocation": "Weet je zeker dat je deze locatie wilt verwijderen?",
@@ -409,9 +417,9 @@
"ButtonRename": "Hernoem",
"ButtonChangeType": "Verander soort",
"HeaderMediaLocations": "Media Locaties",
- "LabelFolderTypeValue": "Map type: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Optioneel: Pad vervanging kan server paden naar netwerk locaties verwijzen zodat clients direct kunnen afspelen.",
- "FolderTypeMixed": "Gemixte films en TV",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "Films",
"FolderTypeMusic": "Muziek",
"FolderTypeAdultVideos": "Adult video's",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Thuis video's",
"FolderTypeGames": "Games",
"FolderTypeBooks": "Boeken",
- "FolderTypeTvShows": "TV programma's",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Films",
"TabSeries": "Serie",
"TabEpisodes": "Afleveringen",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "en kan elke andere Media Browser app bedienen",
"MessageEnjoyYourStay": "Geniet van uw verblijf",
"DashboardTourDashboard": "Het server-dashboard steld u in staat uw server en uw gebruikers te monitoren . U zult altijd weten wie wat doet en waar ze zijn.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Maak gemakkelijk gebruikersaccounts voor uw vrienden en familie, elk met hun eigen machtigingen, bibliotheek toegang, ouderlijk toezicht en meer.",
"DashboardTourCinemaMode": "Cinema mode brengt de theater ervaring naar je woonkamer met de mogelijkheid om trailers en eigen intro's voor de film af te spelen",
"DashboardTourChapters": "Schakel hoofdstuk afbeeldingen genereren in voor uw video's voor een aantrekkelijker presentatie tijdens het kijken.",
@@ -642,7 +651,20 @@
"OptionLow": "Laag",
"HeaderSettings": "Instellingen",
"OptionAutomaticallySyncNewContent": "Nieuwe inhoud automatisch synchroniseren",
- "OptionAutomaticallySyncNewContentHelp": "Nieuwe inhoud in deze mappen zal automatisch gesynchroniseerd worden met het apparaat.",
+ "OptionAutomaticallySyncNewContentHelp": "Nieuwe inhoud toegevoegd aan deze categorie wordt automatisch gesynchroniseerd met het apparaat.",
"OptionSyncUnwatchedVideosOnly": "Synchroniseer alleen ongeziene video's",
- "OptionSyncUnwatchedVideosOnlyHelp": "Alleen ongeziene video's zulle gesynchroniseerd worden en van het apparaat verwijderd worden als ze gezien zijn."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Alleen ongeziene video's zulle gesynchroniseerd worden en van het apparaat verwijderd worden als ze gezien zijn.",
+ "LabelItemLimit": "Item limiet:",
+ "LabelItemLimitHelp": "Optioneel. Een limiet stellen aan het aantal items die zullen worden gesynchroniseerd.",
+ "MessageBookPluginRequired": "Vereist installatie van de Bookshelf plugin",
+ "MessageGamePluginRequired": "Vereist installatie van de GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json
index 98a8b5947..272d82151 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json
@@ -40,6 +40,13 @@
"LabelStopping": "Stopping",
"LabelCancelled": "(cancelled)",
"LabelFailed": "(failed)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "Zapisz",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Library Access",
+ "HeaderChannelAccess": "Channel Access",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(Aborted by server shutdown)",
"LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.",
"HeaderDeleteTaskTrigger": "Delete Task Trigger",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Move right",
"ButtonBrowseOnlineImages": "Browse online images",
"HeaderDeleteItem": "Delete Item",
- "ConfirmDeleteItem": "Are you sure you wish to delete this item from your library?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "Please enter a name or an external Id.",
"MessageValueNotCorrect": "The value entered is not correct. Please try again.",
"MessageItemSaved": "Item saved.",
@@ -396,6 +403,7 @@
"LabelYear": "Year:",
"LabelDateOfBirth": "Date of birth:",
"LabelBirthYear": "Birth year:",
+ "LabelBirthDate": "Birth date:",
"LabelDeathDate": "Death date:",
"HeaderRemoveMediaLocation": "Remove Media Location",
"MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?",
@@ -409,9 +417,9 @@
"ButtonRename": "Rename",
"ButtonChangeType": "Change type",
"HeaderMediaLocations": "Media Locations",
- "LabelFolderTypeValue": "Folder type: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Optional: Path substitution can map server paths to network shares that clients can access for direct playback.",
- "FolderTypeMixed": "Mixed movies & tv",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "Movies",
"FolderTypeMusic": "Music",
"FolderTypeAdultVideos": "Adult videos",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Home videos",
"FolderTypeGames": "Games",
"FolderTypeBooks": "Books",
- "FolderTypeTvShows": "TV shows",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Filmy",
"TabSeries": "Series",
"TabEpisodes": "Odcinki",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "and easily controls other devices and Media Browser apps",
"MessageEnjoyYourStay": "Enjoy your stay",
"DashboardTourDashboard": "The server dashboard allows you to monitor your server and your users. You'll always know who is doing what and where they are.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Easily create user accounts for your friends and family, each with their own permissions, library access, parental controls and more.",
"DashboardTourCinemaMode": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
"DashboardTourChapters": "Enable chapter image generation for your videos for a more pleasing presentation while viewing.",
@@ -642,7 +651,20 @@
"OptionLow": "Low",
"HeaderSettings": "Settings",
"OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
+ "OptionAutomaticallySyncNewContentHelp": "New content added to this category will be automatically synced to the device.",
"OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
- "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
+ "LabelItemLimit": "Item limit:",
+ "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+ "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+ "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json
index 492b402ca..ea5859a22 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json
@@ -40,6 +40,13 @@
"LabelStopping": "Parando",
"LabelCancelled": "(cancelado)",
"LabelFailed": "(falhou)",
+ "ButtonHelp": "Ajuda",
+ "ButtonSave": "Salvar",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Acesso \u00e0 Biblioteca",
+ "HeaderChannelAccess": "Acesso ao Canal",
+ "HeaderDeviceAccess": "Acesso ao Dispositivo",
+ "HeaderSelectDevices": "Selecionar Dispositivos",
"LabelAbortedByServerShutdown": "(Abortada pelo desligamento do servidor)",
"LabelScheduledTaskLastRan": "\u00daltima execu\u00e7\u00e3o {0}, demorando {1}.",
"HeaderDeleteTaskTrigger": "Excluir Disparador da Tarefa",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Mover \u00e0 direita",
"ButtonBrowseOnlineImages": "Procurar imagens online",
"HeaderDeleteItem": "Excluir item",
- "ConfirmDeleteItem": "Deseja realmente excluir este item de sua biblioteca?",
+ "ConfirmDeleteItem": "Excluir este item o excluir\u00e1 do sistema de arquivos e tamb\u00e9m da biblioteca de m\u00eddias. Deseja realmente continuar?",
"MessagePleaseEnterNameOrId": "Por favor, digite um nome ou Id externo.",
"MessageValueNotCorrect": "O valor digitado n\u00e3o est\u00e1 correto. Por favor, tente novamente.",
"MessageItemSaved": "Item salvo.",
@@ -396,6 +403,7 @@
"LabelYear": "Ano:",
"LabelDateOfBirth": "Data de nascimento:",
"LabelBirthYear": "Ano de nascimento:",
+ "LabelBirthDate": "Data de nascimento:",
"LabelDeathDate": "Data da morte:",
"HeaderRemoveMediaLocation": "Remover Localiza\u00e7\u00e3o da M\u00eddia",
"MessageConfirmRemoveMediaLocation": "Deseja realmente remover esta localiza\u00e7\u00e3o?",
@@ -409,9 +417,9 @@
"ButtonRename": "Renomear",
"ButtonChangeType": "Alterar tipo",
"HeaderMediaLocations": "Localiza\u00e7\u00f5es de M\u00eddia",
- "LabelFolderTypeValue": "Tipo de pasta: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Opcional: Substitui\u00e7\u00e3o de caminho pode mapear caminhos do servidor para compartilhamentos de rede de forma a que os clientes possam acessar para reprodu\u00e7\u00e3o direta.",
- "FolderTypeMixed": "Filmes & tv misturados",
+ "FolderTypeUnset": "Indefinida (conte\u00fado misto)",
"FolderTypeMovies": "Filmes",
"FolderTypeMusic": "M\u00fasica",
"FolderTypeAdultVideos": "V\u00eddeos adultos",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "V\u00eddeos caseiros",
"FolderTypeGames": "Jogos",
"FolderTypeBooks": "Livros",
- "FolderTypeTvShows": "S\u00e9ries de TV",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Filmes",
"TabSeries": "S\u00e9ries",
"TabEpisodes": "Epis\u00f3dios",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "e controle facilmente outros dispositivos e apps do Media Browser",
"MessageEnjoyYourStay": "Divirta-se",
"DashboardTourDashboard": "O painel do servidor permite monitorar seu servidor e seus usu\u00e1rios. Voc\u00ea sempre poder\u00e1 saber quem est\u00e1 fazendo o qu\u00ea e onde est\u00e3o.",
+ "DashboardTourHelp": "A ajuda dentro da app fornece bot\u00f5es para abrir p\u00e1ginas wiki relacionadas ao conte\u00fado na tela.",
"DashboardTourUsers": "Crie facilmente contas de usu\u00e1rios para seus amigos e fam\u00edlia, cada um com sua permiss\u00e3o, acesso \u00e0 biblioteca, controle parental e mais.",
"DashboardTourCinemaMode": "O modo cinema traz a experi\u00eancia do cinema para sua sala, permitindo reproduzir trailers e intros personalizadas antes da fun\u00e7\u00e3o principal.",
"DashboardTourChapters": "Ative a gera\u00e7\u00e3o de imagem do cap\u00edtulo para seus v\u00eddeos para ter uma apresenta\u00e7\u00e3o mais prazeirosa.",
@@ -642,7 +651,20 @@
"OptionLow": "Baixa",
"HeaderSettings": "Ajustes",
"OptionAutomaticallySyncNewContent": "Sincronizar novo conte\u00fado automaticamente",
- "OptionAutomaticallySyncNewContentHelp": "Novo conte\u00fado adicionado a estas pastas ser\u00e1 automaticamente sincronizado com o dispositivo.",
+ "OptionAutomaticallySyncNewContentHelp": "Novo conte\u00fado adicionado a esta categoria ser\u00e1 automaticamente sincronizado com o dispositivo.",
"OptionSyncUnwatchedVideosOnly": "Sincronizar apenas v\u00eddeos n\u00e3o assistidos",
- "OptionSyncUnwatchedVideosOnlyHelp": "Apenas v\u00eddeos n\u00e3o assistidos ser\u00e3o sincronizados, e os v\u00eddeos ser\u00e3o removidos do dispositivo assim que forem assistidos."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Apenas v\u00eddeos n\u00e3o assistidos ser\u00e3o sincronizados, e os v\u00eddeos ser\u00e3o removidos do dispositivo assim que forem assistidos.",
+ "LabelItemLimit": "Limite de itens:",
+ "LabelItemLimitHelp": "Opcional. Defina o n\u00famero limite de itens que ser\u00e3o sincronizados.",
+ "MessageBookPluginRequired": "Requer a instala\u00e7\u00e3o do plugin Bookshelf",
+ "MessageGamePluginRequired": "Requer a instala\u00e7\u00e3o do plugin GameBrowser",
+ "MessageUnsetContentHelp": "O conte\u00fado ser\u00e1 exibido em pastas simples. Para melhor resultado, use o gerenciador de metadados para definir os tipos de conte\u00fado das sub-pastas.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json
index 5007ae444..f0b73065f 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json
@@ -36,10 +36,17 @@
"LabelMovie": "Movie",
"LabelMusicVideo": "Music Video",
"LabelEpisode": "Episode",
- "LabelSeries": "Series",
+ "LabelSeries": "S\u00e9rie",
"LabelStopping": "Stopping",
"LabelCancelled": "(cancelled)",
- "LabelFailed": "(failed)",
+ "LabelFailed": "(falhou)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "Guardar",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Library Access",
+ "HeaderChannelAccess": "Channel Access",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(Aborted by server shutdown)",
"LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.",
"HeaderDeleteTaskTrigger": "Delete Task Trigger",
@@ -63,7 +70,7 @@
"ButtonPlay": "Reproduzir",
"ButtonEdit": "Editar",
"ButtonQueue": "Queue",
- "ButtonPlayTrailer": "Play trailer",
+ "ButtonPlayTrailer": "Reproduzir trailer",
"ButtonPlaylist": "Playlist",
"ButtonPreviousTrack": "Faixa Anterior",
"LabelEnabled": "Enabled",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Move right",
"ButtonBrowseOnlineImages": "Browse online images",
"HeaderDeleteItem": "Delete Item",
- "ConfirmDeleteItem": "Are you sure you wish to delete this item from your library?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "Please enter a name or an external Id.",
"MessageValueNotCorrect": "The value entered is not correct. Please try again.",
"MessageItemSaved": "Item saved.",
@@ -384,7 +391,7 @@
"PersonTypePerson": "Person",
"LabelTitleDisplayOrder": "Title display order:",
"OptionSortName": "Sort name",
- "OptionReleaseDate": "Release date",
+ "OptionReleaseDate": "Data de lan\u00e7amento",
"LabelSeasonNumber": "N\u00famero da temporada:",
"LabelDiscNumber": "Disc number",
"LabelParentNumber": "Parent number",
@@ -396,6 +403,7 @@
"LabelYear": "Year:",
"LabelDateOfBirth": "Date of birth:",
"LabelBirthYear": "Birth year:",
+ "LabelBirthDate": "Birth date:",
"LabelDeathDate": "Death date:",
"HeaderRemoveMediaLocation": "Remove Media Location",
"MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?",
@@ -409,9 +417,9 @@
"ButtonRename": "Rename",
"ButtonChangeType": "Change type",
"HeaderMediaLocations": "Media Locations",
- "LabelFolderTypeValue": "Folder type: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Optional: Path substitution can map server paths to network shares that clients can access for direct playback.",
- "FolderTypeMixed": "Mixed movies & tv",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "Movies",
"FolderTypeMusic": "Music",
"FolderTypeAdultVideos": "Adult videos",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Home videos",
"FolderTypeGames": "Games",
"FolderTypeBooks": "Books",
- "FolderTypeTvShows": "TV shows",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Filmes",
"TabSeries": "S\u00e9ries",
"TabEpisodes": "Epis\u00f3dios",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "and easily controls other devices and Media Browser apps",
"MessageEnjoyYourStay": "Enjoy your stay",
"DashboardTourDashboard": "The server dashboard allows you to monitor your server and your users. You'll always know who is doing what and where they are.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Easily create user accounts for your friends and family, each with their own permissions, library access, parental controls and more.",
"DashboardTourCinemaMode": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
"DashboardTourChapters": "Enable chapter image generation for your videos for a more pleasing presentation while viewing.",
@@ -642,7 +651,20 @@
"OptionLow": "Low",
"HeaderSettings": "Settings",
"OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
+ "OptionAutomaticallySyncNewContentHelp": "New content added to this category will be automatically synced to the device.",
"OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
- "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
+ "LabelItemLimit": "Item limit:",
+ "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+ "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+ "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json
index 15e489fb3..0e7c182a5 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json
@@ -40,6 +40,13 @@
"LabelStopping": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430",
"LabelCancelled": "(\u043e\u0442\u043c\u0435\u043d\u0435\u043d\u043e)",
"LabelFailed": "(\u043d\u0435\u0443\u0434\u0430\u0447\u043d\u043e)",
+ "ButtonHelp": "\u0421\u043f\u0440\u0430\u0432\u043a\u0430",
+ "ButtonSave": "\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "\u0414\u043e\u0441\u0442\u0443\u043f \u043a \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0435",
+ "HeaderChannelAccess": "\u0414\u043e\u0441\u0442\u0443\u043f \u043a \u043a\u0430\u043d\u0430\u043b\u0430\u043c",
+ "HeaderDeviceAccess": "\u0414\u043e\u0441\u0442\u0443\u043f \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430",
+ "HeaderSelectDevices": "\u0412\u044b\u0431\u043e\u0440 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430",
"LabelAbortedByServerShutdown": "(\u041f\u0440\u0435\u0440\u0432\u0430\u043d\u043e \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\u043c \u0441\u0435\u0440\u0432\u0435\u0440\u0430)",
"LabelScheduledTaskLastRan": "\u0412\u044b\u043f\u043e\u043b\u043d\u044f\u043b\u0430\u0441\u044c {0}, \u0437\u0430\u043d\u044f\u043b\u0430 {1}.",
"HeaderDeleteTaskTrigger": "\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0442\u0440\u0438\u0433\u0433\u0435\u0440\u0430 \u0437\u0430\u0434\u0430\u0447\u0438",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "\u0414\u0432\u0438\u0433\u0430\u0442\u044c \u0432\u043f\u0440\u0430\u0432\u043e",
"ButtonBrowseOnlineImages": "\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0440\u0438\u0441\u0443\u043d\u043a\u0438 \u0432 \u0441\u0435\u0442\u0438",
"HeaderDeleteItem": "\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430",
- "ConfirmDeleteItem": "\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0438\u0437 \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0438?",
+ "ConfirmDeleteItem": "\u041f\u0440\u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0438 \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430, \u043e\u043d \u0443\u0434\u0430\u043b\u0438\u0442\u0441\u044f \u0438 \u0438\u0437 \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b, \u0438 \u0438\u0437 \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0438. \u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c?",
"MessagePleaseEnterNameOrId": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0438\u043b\u0438 \u0432\u043d\u0435\u0448\u043d\u0438\u0439 ID.",
"MessageValueNotCorrect": "\u0412\u0432\u0435\u0434\u0451\u043d\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043d\u0435 \u0432\u0435\u0440\u043d\u043e. \u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443.",
"MessageItemSaved": "\u042d\u043b\u0435\u043c\u0435\u043d\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u0451\u043d.",
@@ -396,6 +403,7 @@
"LabelYear": "\u0413\u043e\u0434:",
"LabelDateOfBirth": "\u0414\u0430\u0442\u0430 \u0440\u043e\u0436\u0434\u0435\u043d\u0438\u044f:",
"LabelBirthYear": "\u0413\u043e\u0434 \u0440\u043e\u0436\u0434\u0435\u043d\u0438\u044f:",
+ "LabelBirthDate": "\u0414\u0430\u0442\u0430 \u0440\u043e\u0436\u0434\u0435\u043d\u0438\u044f:",
"LabelDeathDate": "\u0414\u0430\u0442\u0430 \u0441\u043c\u0435\u0440\u0442\u0438:",
"HeaderRemoveMediaLocation": "\u0418\u0437\u044a\u044f\u0442\u0438\u0435 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043c\u0435\u0434\u0438\u0430\u0434\u0430\u043d\u043d\u044b\u0445",
"MessageConfirmRemoveMediaLocation": "\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u0438\u0437\u044a\u044f\u0442\u044c \u044d\u0442\u043e \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435?",
@@ -409,9 +417,9 @@
"ButtonRename": "\u041f\u0435\u0440\u0435\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u0442\u044c",
"ButtonChangeType": "\u0421\u043c\u0435\u043d\u0438\u0442\u044c \u0442\u0438\u043f",
"HeaderMediaLocations": "\u0420\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043c\u0435\u0434\u0438\u0430\u0434\u0430\u043d\u043d\u044b\u0445",
- "LabelFolderTypeValue": "\u0422\u0438\u043f \u043f\u0430\u043f\u043a\u0438: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "\u041d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e: \u041f\u043e\u0434\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043f\u0443\u0442\u0435\u0439 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0445 \u043f\u0443\u0442\u0435\u0439 \u0441\u043e \u0441\u0435\u0442\u0435\u0432\u044b\u043c\u0438 \u043e\u0431\u0449\u0438\u043c\u0438 \u0440\u0435\u0441\u0443\u0440\u0441\u0430\u043c\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b \u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043c \u0434\u043b\u044f \u043f\u0440\u044f\u043c\u043e\u0433\u043e \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f.",
- "FolderTypeMixed": "\u0421\u043c\u0435\u0448\u0430\u043d\u043d\u044b\u0439 (\u0444\u0438\u043b\u044c\u043c\u044b \u0438 \u0422\u0412)",
+ "FolderTypeUnset": "\u041d\u0435\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044b\u0439 (\u0440\u0430\u0437\u043d\u043e\u0442\u0438\u043f\u043d\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435)",
"FolderTypeMovies": "\u0424\u0438\u043b\u044c\u043c\u044b",
"FolderTypeMusic": "\u041c\u0443\u0437\u044b\u043a\u0430",
"FolderTypeAdultVideos": "\u0412\u0437\u0440\u043e\u0441\u043b\u044b\u0435 \u0432\u0438\u0434\u0435\u043e",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "\u0414\u043e\u043c\u0430\u0448\u043d\u0438\u0435 \u0432\u0438\u0434\u0435\u043e",
"FolderTypeGames": "\u0418\u0433\u0440\u044b",
"FolderTypeBooks": "\u041a\u043d\u0438\u0433\u0438",
- "FolderTypeTvShows": "\u0422\u0412 \u0446\u0438\u043a\u043b\u044b",
+ "FolderTypeTvShows": "\u0422\u0412",
"TabMovies": "\u0424\u0438\u043b\u044c\u043c\u044b",
"TabSeries": "\u0421\u0435\u0440\u0438\u0430\u043b\u044b",
"TabEpisodes": "\u042d\u043f\u0438\u0437\u043e\u0434\u044b",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "\u0438 \u0441 \u043b\u0435\u0433\u043a\u043e\u0441\u0442\u044c\u044e \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438 \u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u043c\u0438 Media Browser",
"MessageEnjoyYourStay": "\u041f\u0440\u0438\u044f\u0442\u043d\u043e\u0433\u043e \u0432\u0440\u0435\u043c\u044f\u043f\u0440\u043e\u0432\u043e\u0436\u0434\u0435\u043d\u0438\u044f",
"DashboardTourDashboard": "\u0421\u0435\u0440\u0432\u0435\u0440\u043d\u0430\u044f \u0418\u043d\u0444\u043e\u043f\u0430\u043d\u0435\u043b\u044c \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043f\u043e \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u044e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439. \u0412\u044b \u0431\u0443\u0434\u0435\u0442\u0435 \u0432\u0441\u0435\u0433\u0434\u0430 \u0437\u043d\u0430\u0442\u044c, \u043a\u0442\u043e \u0437\u0430\u043d\u0438\u043c\u0430\u0435\u0442\u0441\u044f \u0447\u0435\u043c \u0438 \u0433\u0434\u0435 \u043e\u043d\u0438 \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f.",
+ "DashboardTourHelp": "\u0412\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u0430\u044f \u0441\u043f\u0440\u0430\u0432\u043a\u0430 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043f\u0440\u043e\u0441\u0442\u044b\u0435 \u043a\u043d\u043e\u043f\u043a\u0438, \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0432\u0438\u043a\u0438-\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u043e\u0442\u043d\u043e\u0441\u044f\u0449\u0438\u0445\u0441\u044f \u043a \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044e \u044d\u043a\u0440\u0430\u043d\u0430.",
"DashboardTourUsers": "\u0421\u0432\u043e\u0431\u043e\u0434\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0443\u0447\u0451\u0442\u043d\u044b\u0435 \u0437\u0430\u043f\u0438\u0441\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0434\u043b\u044f \u0432\u0430\u0448\u0438\u0445 \u0434\u0440\u0443\u0437\u0435\u0439 \u0438 \u0447\u043b\u0435\u043d\u043e\u0432 \u0441\u0435\u043c\u044c\u0438, \u043a\u0430\u0436\u0434\u0443\u044e \u0441 \u0438\u0445 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u043c\u0438 \u043f\u0440\u0430\u0432\u0430\u043c\u0438, \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u043a \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0435, \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043c \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435\u043c \u0438 \u0442.\u0434.",
"DashboardTourCinemaMode": "\u0420\u0435\u0436\u0438\u043c \u043a\u0438\u043d\u043e\u0442\u0435\u0430\u0442\u0440\u0430 \u043f\u0440\u0438\u0432\u043d\u043e\u0441\u0438\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043a\u0438\u043d\u043e\u0437\u0430\u043b\u0430 \u043f\u0440\u044f\u043c\u0438\u043a\u043e\u043c \u0432 \u0432\u0430\u0448\u0443 \u0433\u043e\u0441\u0442\u0438\u043d\u0443\u044e, \u0432\u043c\u0435\u0441\u0442\u0435 \u0441\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044c\u044e \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u044c \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u044b \u0438 \u043d\u0435\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0435 \u0437\u0430\u0441\u0442\u0430\u0432\u043a\u0438 \u043f\u0435\u0440\u0435\u0434 \u0433\u043b\u0430\u0432\u043d\u044b\u043c \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u043e\u043c.",
"DashboardTourChapters": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u0435 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0440\u0438\u0441\u0443\u043d\u043a\u043e\u0432 \u0441\u0446\u0435\u043d \u043a \u0432\u0438\u0434\u0435\u043e, \u0434\u043b\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u0438\u0432\u043b\u0435\u043a\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u043f\u0440\u0435\u0437\u0435\u043d\u0442\u0430\u0446\u0438\u0438 \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430.",
@@ -641,8 +650,21 @@
"OptionMedium": "\u0421\u0440\u0435\u0434\u043d\u0435\u0435",
"OptionLow": "\u041d\u0438\u0437\u043a\u043e\u0435",
"HeaderSettings": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b",
- "OptionAutomaticallySyncNewContent": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435",
- "OptionAutomaticallySyncNewContentHelp": "\u041d\u043e\u0432\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435, \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u0435 \u0432 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u0430\u043f\u043a\u0438, \u0431\u0443\u0434\u0435\u0442 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0441 \u0434\u0430\u043d\u043d\u044b\u043c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c.",
- "OptionSyncUnwatchedVideosOnly": "\u0421\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0435\u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u043d\u044b\u0435 \u0432\u0438\u0434\u0435\u043e\u0444\u0430\u0439\u043b\u044b.",
- "OptionSyncUnwatchedVideosOnlyHelp": "\u0422\u043e\u043b\u044c\u043a\u043e \u043d\u0435\u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u043d\u044b\u0435 \u0432\u0438\u0434\u0435\u043e\u0444\u0430\u0439\u043b\u044b \u0431\u0443\u0434\u0443\u0442 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f, \u0430 \u0432\u0438\u0434\u0435\u043e\u0444\u0430\u0439\u043b\u044b \u0431\u0443\u0434\u0443\u0442 \u0438\u0437\u044b\u043c\u0430\u0442\u044c\u0441\u044f \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043f\u043e\u0441\u043b\u0435 \u0438\u0445 \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430."
+ "OptionAutomaticallySyncNewContent": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0441\u0438\u043d\u0445\u0440-\u0442\u044c \u043d\u043e\u0432\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435",
+ "OptionAutomaticallySyncNewContentHelp": "\u041d\u043e\u0432\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435, \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u0435 \u0432 \u0434\u0430\u043d\u043d\u0443\u044e \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044e, \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c.",
+ "OptionSyncUnwatchedVideosOnly": "\u0421\u0438\u043d\u0445\u0440-\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0435\u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u043d\u044b\u0435 \u0432\u0438\u0434\u0435\u043e",
+ "OptionSyncUnwatchedVideosOnlyHelp": "\u0421\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0435\u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u043d\u044b\u0435 \u0432\u0438\u0434\u0435\u043e, \u0430 \u0441\u0430\u043c\u0438 \u0432\u0438\u0434\u0435\u043e \u0438\u0437\u044b\u043c\u0430\u044e\u0442\u0441\u044f \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043f\u043e\u0441\u043b\u0435 \u0438\u0445 \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430.",
+ "LabelItemLimit": "\u041f\u0440\u0435\u0434\u0435\u043b \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432:",
+ "LabelItemLimitHelp": "\u041d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e. \u0417\u0430\u0434\u0430\u0442\u044c \u043f\u0440\u0435\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u0447\u0438\u0441\u043b\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0443\u0442 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f.",
+ "MessageBookPluginRequired": "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043f\u043b\u0430\u0433\u0438\u043d\u0430 Bookshelf",
+ "MessageGamePluginRequired": "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043f\u043b\u0430\u0433\u0438\u043d\u0430 GameBrowser",
+ "MessageUnsetContentHelp": "\u0421\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u044b\u0435 \u043f\u0430\u043f\u043a\u0438. \u0414\u043b\u044f \u043d\u0430\u0438\u043b\u0443\u0447\u0448\u0438\u0445 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0434\u0438\u0441\u043f\u0435\u0442\u0447\u0435\u0440 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0437\u043d\u0430\u0447\u0430\u0442\u044c \u0442\u0438\u043f \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f \u043f\u043e\u0434\u043f\u0430\u043f\u043e\u043a.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json
index a0b86f576..2430e76bc 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json
@@ -40,6 +40,13 @@
"LabelStopping": "Avbryter",
"LabelCancelled": "(avbr\u00f6ts)",
"LabelFailed": "(misslyckades)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "Spara",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Library Access",
+ "HeaderChannelAccess": "Channel Access",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(avbr\u00f6ts eftersom servern st\u00e4ngdes av)",
"LabelScheduledTaskLastRan": "Senast k\u00f6rd {0}, tog {1}",
"HeaderDeleteTaskTrigger": "Ta bort aktivitetsutl\u00f6sare",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "H\u00f6ger",
"ButtonBrowseOnlineImages": "Bl\u00e4ddra bland bilder online",
"HeaderDeleteItem": "Radera objekt",
- "ConfirmDeleteItem": "\u00c4r du s\u00e4ker p\u00e5 att du vill radera det h\u00e4r objektet fr\u00e5n biblioteket?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "Ange ett namn eller externt id.",
"MessageValueNotCorrect": "Det angivna v\u00e4rdet \u00e4r felaktigt. Var god f\u00f6rs\u00f6k igen.",
"MessageItemSaved": "Objektet har sparats.",
@@ -396,6 +403,7 @@
"LabelYear": "\u00c5r:",
"LabelDateOfBirth": "F\u00f6delsedatum:",
"LabelBirthYear": "F\u00f6delse\u00e5r:",
+ "LabelBirthDate": "Birth date:",
"LabelDeathDate": "D\u00f6d:",
"HeaderRemoveMediaLocation": "Ta bort mediaplats",
"MessageConfirmRemoveMediaLocation": "\u00c4r du s\u00e4ker p\u00e5 att du vill ta bort den h\u00e4r platsen?",
@@ -409,9 +417,9 @@
"ButtonRename": "\u00c4ndra namn",
"ButtonChangeType": "\u00c4ndra typ",
"HeaderMediaLocations": "Lagringsplatser f\u00f6r media",
- "LabelFolderTypeValue": "Typ av mapp: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Tillval: S\u00f6kv\u00e4gsutbyte betyder att en plats p\u00e5 servern kopplas till en lokal fils\u00f6kv\u00e4g p\u00e5 en klient. P\u00e5 s\u00e5 s\u00e4tt f\u00e5r klienten direkt tillg\u00e5ng till material p\u00e5 servern och kan spela upp det direkt via n\u00e4tverket.",
- "FolderTypeMixed": "Blandat film & TV",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "Filmer",
"FolderTypeMusic": "Musik",
"FolderTypeAdultVideos": "Inneh\u00e5ll f\u00f6r vuxna",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Hemvideor",
"FolderTypeGames": "Spel",
"FolderTypeBooks": "B\u00f6cker",
- "FolderTypeTvShows": "TV-serier",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Filmer",
"TabSeries": "Serie",
"TabEpisodes": "Avsnitt",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "och kan enkelt fj\u00e4rrstyra andra enheter och Media Browser-appar",
"MessageEnjoyYourStay": "Ha ett trevligt bes\u00f6k",
"DashboardTourDashboard": "Via serverns kontrollpanel kan du \u00f6vervaka din server och alla anv\u00e4ndare. Du kommer alltid att kunna veta vem som g\u00f6r vad och var de \u00e4r.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Skapa enkelt anv\u00e4ndarkonton f\u00f6r dina v\u00e4nner och familj, alla med sina egna beh\u00f6righeter, biblioteks \u00e5tkomst, f\u00f6r\u00e4ldrakontroll och mycket mer.",
"DashboardTourCinemaMode": "Biol\u00e4get g\u00f6r ditt vardagsrum till en biograf genom m\u00f6jligheten att visa trailers och egna vinjetter innan filmen b\u00f6rjar.",
"DashboardTourChapters": "Ta fram kapitelbildrutor fr\u00e5n videofiler f\u00f6r att f\u00e5 en snyggare presentation.",
@@ -642,7 +651,20 @@
"OptionLow": "Low",
"HeaderSettings": "Settings",
"OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
+ "OptionAutomaticallySyncNewContentHelp": "New content added to this category will be automatically synced to the device.",
"OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
- "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
+ "LabelItemLimit": "Item limit:",
+ "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+ "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+ "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/tr.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/tr.json
index e8ecf0de0..f4894db81 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/tr.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/tr.json
@@ -40,6 +40,13 @@
"LabelStopping": "Stopping",
"LabelCancelled": "(cancelled)",
"LabelFailed": "(failed)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "Kay\u0131t",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Library Access",
+ "HeaderChannelAccess": "Channel Access",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(Aborted by server shutdown)",
"LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.",
"HeaderDeleteTaskTrigger": "Delete Task Trigger",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Move right",
"ButtonBrowseOnlineImages": "Browse online images",
"HeaderDeleteItem": "Delete Item",
- "ConfirmDeleteItem": "Are you sure you wish to delete this item from your library?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "Please enter a name or an external Id.",
"MessageValueNotCorrect": "The value entered is not correct. Please try again.",
"MessageItemSaved": "Item saved.",
@@ -396,6 +403,7 @@
"LabelYear": "Year:",
"LabelDateOfBirth": "Date of birth:",
"LabelBirthYear": "Birth year:",
+ "LabelBirthDate": "Birth date:",
"LabelDeathDate": "Death date:",
"HeaderRemoveMediaLocation": "Remove Media Location",
"MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?",
@@ -409,9 +417,9 @@
"ButtonRename": "Rename",
"ButtonChangeType": "Change type",
"HeaderMediaLocations": "Media Locations",
- "LabelFolderTypeValue": "Folder type: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Optional: Path substitution can map server paths to network shares that clients can access for direct playback.",
- "FolderTypeMixed": "Mixed movies & tv",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "Movies",
"FolderTypeMusic": "Music",
"FolderTypeAdultVideos": "Adult videos",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Home videos",
"FolderTypeGames": "Games",
"FolderTypeBooks": "Books",
- "FolderTypeTvShows": "TV shows",
+ "FolderTypeTvShows": "TV",
"TabMovies": "Filmler",
"TabSeries": "Seriler",
"TabEpisodes": "B\u00f6l\u00fcmler",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "and easily controls other devices and Media Browser apps",
"MessageEnjoyYourStay": "Enjoy your stay",
"DashboardTourDashboard": "The server dashboard allows you to monitor your server and your users. You'll always know who is doing what and where they are.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Easily create user accounts for your friends and family, each with their own permissions, library access, parental controls and more.",
"DashboardTourCinemaMode": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
"DashboardTourChapters": "Enable chapter image generation for your videos for a more pleasing presentation while viewing.",
@@ -642,7 +651,20 @@
"OptionLow": "Low",
"HeaderSettings": "Settings",
"OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
+ "OptionAutomaticallySyncNewContentHelp": "New content added to this category will be automatically synced to the device.",
"OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
- "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
+ "LabelItemLimit": "Item limit:",
+ "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+ "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+ "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json
index e94761156..4ca41d62a 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json
@@ -40,6 +40,13 @@
"LabelStopping": "Stopping",
"LabelCancelled": "(cancelled)",
"LabelFailed": "(failed)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "L\u01b0u",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Library Access",
+ "HeaderChannelAccess": "Channel Access",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(Aborted by server shutdown)",
"LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.",
"HeaderDeleteTaskTrigger": "Delete Task Trigger",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Move right",
"ButtonBrowseOnlineImages": "Browse online images",
"HeaderDeleteItem": "Delete Item",
- "ConfirmDeleteItem": "Are you sure you wish to delete this item from your library?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "Please enter a name or an external Id.",
"MessageValueNotCorrect": "The value entered is not correct. Please try again.",
"MessageItemSaved": "Item saved.",
@@ -396,6 +403,7 @@
"LabelYear": "Year:",
"LabelDateOfBirth": "Date of birth:",
"LabelBirthYear": "Birth year:",
+ "LabelBirthDate": "Birth date:",
"LabelDeathDate": "Death date:",
"HeaderRemoveMediaLocation": "Remove Media Location",
"MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?",
@@ -409,9 +417,9 @@
"ButtonRename": "Rename",
"ButtonChangeType": "Change type",
"HeaderMediaLocations": "Media Locations",
- "LabelFolderTypeValue": "Folder type: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Optional: Path substitution can map server paths to network shares that clients can access for direct playback.",
- "FolderTypeMixed": "Mixed movies & tv",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "Movies",
"FolderTypeMusic": "Music",
"FolderTypeAdultVideos": "Adult videos",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Home videos",
"FolderTypeGames": "Games",
"FolderTypeBooks": "Books",
- "FolderTypeTvShows": "TV shows",
+ "FolderTypeTvShows": "TV",
"TabMovies": "C\u00e1c phim",
"TabSeries": "Series",
"TabEpisodes": "C\u00e1c t\u1eadp phim",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "and easily controls other devices and Media Browser apps",
"MessageEnjoyYourStay": "Enjoy your stay",
"DashboardTourDashboard": "The server dashboard allows you to monitor your server and your users. You'll always know who is doing what and where they are.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Easily create user accounts for your friends and family, each with their own permissions, library access, parental controls and more.",
"DashboardTourCinemaMode": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
"DashboardTourChapters": "Enable chapter image generation for your videos for a more pleasing presentation while viewing.",
@@ -642,7 +651,20 @@
"OptionLow": "Low",
"HeaderSettings": "Settings",
"OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
+ "OptionAutomaticallySyncNewContentHelp": "New content added to this category will be automatically synced to the device.",
"OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
- "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
+ "LabelItemLimit": "Item limit:",
+ "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+ "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+ "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_CN.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_CN.json
index ad9a6c6a5..99d1557ae 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_CN.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_CN.json
@@ -40,6 +40,13 @@
"LabelStopping": "\u505c\u6b62",
"LabelCancelled": "(\u5df2\u53d6\u6d88)",
"LabelFailed": "(\u5931\u8d25)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "\u50a8\u5b58",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Library Access",
+ "HeaderChannelAccess": "Channel Access",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(\u56e0\u4e3a\u670d\u52a1\u5668\u5173\u95ed\u88ab\u4e2d\u6b62)",
"LabelScheduledTaskLastRan": "\u6700\u540e\u8fd0\u884c {0}, \u82b1\u8d39\u65f6\u95f4 {1}.",
"HeaderDeleteTaskTrigger": "\u5220\u9664\u4efb\u52a1\u89e6\u53d1\u6761\u4ef6",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "\u53f3\u79fb",
"ButtonBrowseOnlineImages": "\u6d4f\u89c8\u5728\u7ebf\u56fe\u7247",
"HeaderDeleteItem": "\u5220\u9664\u9879\u76ee",
- "ConfirmDeleteItem": "\u4f60\u786e\u5b9a\u5e0c\u671b\u4ece\u5a92\u4f53\u5e93\u91cc\u5220\u9664\u8fd9\u4e2a\u9879\u76ee\uff1f",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "\u8bf7\u8f93\u5165\u4e00\u4e2a\u540d\u79f0\u6216\u4e00\u4e2a\u5916\u90e8ID\u3002",
"MessageValueNotCorrect": "\u8f93\u5165\u7684\u503c\u4e0d\u6b63\u786e\u3002\u8bf7\u91cd\u8bd5\u3002",
"MessageItemSaved": "\u9879\u76ee\u5df2\u4fdd\u5b58\u3002",
@@ -396,6 +403,7 @@
"LabelYear": "\u5e74\uff1a",
"LabelDateOfBirth": "\u51fa\u751f\u65e5\u671f\uff1a",
"LabelBirthYear": "\u51fa\u751f\u5e74\u4efd\uff1a",
+ "LabelBirthDate": "Birth date:",
"LabelDeathDate": "\u53bb\u4e16\u65e5\u671f\uff1a",
"HeaderRemoveMediaLocation": "\u79fb\u9664\u5a92\u4f53\u4f4d\u7f6e",
"MessageConfirmRemoveMediaLocation": "\u4f60\u786e\u5b9a\u8981\u79fb\u9664\u6b64\u4f4d\u7f6e\uff1f",
@@ -409,9 +417,9 @@
"ButtonRename": "\u91cd\u547d\u540d",
"ButtonChangeType": "\u53d8\u66f4\u683c\u5f0f",
"HeaderMediaLocations": "\u5a92\u4f53\u4f4d\u7f6e",
- "LabelFolderTypeValue": "\u6587\u4ef6\u5939\u7c7b\u578b\uff1a {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "\u53ef\u9009\uff1a\u66ff\u4ee3\u8def\u5f84\u80fd\u628a\u670d\u52a1\u5668\u8def\u5f84\u6620\u5c04\u5230\u7f51\u7edc\u5171\u4eab\uff0c\u4ece\u800c\u4f7f\u5ba2\u6237\u7aef\u53ef\u4ee5\u76f4\u63a5\u64ad\u653e\u3002",
- "FolderTypeMixed": "\u6df7\u5408\u7684\u7535\u5f71\u548c\u7535\u89c6",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "\u7535\u5f71",
"FolderTypeMusic": "\u97f3\u4e50",
"FolderTypeAdultVideos": "\u6210\u4eba\u89c6\u9891",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "\u5bb6\u5ead\u89c6\u9891",
"FolderTypeGames": "\u6e38\u620f",
"FolderTypeBooks": "\u4e66\u7c4d",
- "FolderTypeTvShows": "\u7535\u89c6\u8282\u76ee",
+ "FolderTypeTvShows": "TV",
"TabMovies": "\u7535\u5f71",
"TabSeries": "\u7535\u89c6\u5267",
"TabEpisodes": "\u5267\u96c6",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "and easily controls other devices and Media Browser apps",
"MessageEnjoyYourStay": "Enjoy your stay",
"DashboardTourDashboard": "The server dashboard allows you to monitor your server and your users. You'll always know who is doing what and where they are.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Easily create user accounts for your friends and family, each with their own permissions, library access, parental controls and more.",
"DashboardTourCinemaMode": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
"DashboardTourChapters": "Enable chapter image generation for your videos for a more pleasing presentation while viewing.",
@@ -642,7 +651,20 @@
"OptionLow": "Low",
"HeaderSettings": "Settings",
"OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
+ "OptionAutomaticallySyncNewContentHelp": "New content added to this category will be automatically synced to the device.",
"OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
- "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
+ "LabelItemLimit": "Item limit:",
+ "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+ "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+ "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json
index 706299704..4976850f6 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json
@@ -40,6 +40,13 @@
"LabelStopping": "Stopping",
"LabelCancelled": "(cancelled)",
"LabelFailed": "(failed)",
+ "ButtonHelp": "Help",
+ "ButtonSave": "\u4fdd\u5b58",
+ "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.",
+ "HeaderLibraryAccess": "Library Access",
+ "HeaderChannelAccess": "Channel Access",
+ "HeaderDeviceAccess": "Device Access",
+ "HeaderSelectDevices": "Select Devices",
"LabelAbortedByServerShutdown": "(Aborted by server shutdown)",
"LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.",
"HeaderDeleteTaskTrigger": "Delete Task Trigger",
@@ -249,7 +256,7 @@
"ButtonMoveRight": "Move right",
"ButtonBrowseOnlineImages": "Browse online images",
"HeaderDeleteItem": "Delete Item",
- "ConfirmDeleteItem": "Are you sure you wish to delete this item from your library?",
+ "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"MessagePleaseEnterNameOrId": "Please enter a name or an external Id.",
"MessageValueNotCorrect": "The value entered is not correct. Please try again.",
"MessageItemSaved": "Item saved.",
@@ -396,6 +403,7 @@
"LabelYear": "Year:",
"LabelDateOfBirth": "Date of birth:",
"LabelBirthYear": "Birth year:",
+ "LabelBirthDate": "Birth date:",
"LabelDeathDate": "Death date:",
"HeaderRemoveMediaLocation": "Remove Media Location",
"MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?",
@@ -409,9 +417,9 @@
"ButtonRename": "Rename",
"ButtonChangeType": "Change type",
"HeaderMediaLocations": "Media Locations",
- "LabelFolderTypeValue": "Folder type: {0}",
+ "LabelContentTypeValue": "Content type: {0}",
"LabelPathSubstitutionHelp": "Optional: Path substitution can map server paths to network shares that clients can access for direct playback.",
- "FolderTypeMixed": "Mixed movies & tv",
+ "FolderTypeUnset": "Unset (mixed content)",
"FolderTypeMovies": "Movies",
"FolderTypeMusic": "Music",
"FolderTypeAdultVideos": "Adult videos",
@@ -420,7 +428,7 @@
"FolderTypeHomeVideos": "Home videos",
"FolderTypeGames": "Games",
"FolderTypeBooks": "Books",
- "FolderTypeTvShows": "TV shows",
+ "FolderTypeTvShows": "TV",
"TabMovies": "\u96fb\u5f71",
"TabSeries": "\u96fb\u8996\u5287",
"TabEpisodes": "\u55ae\u5143",
@@ -587,6 +595,7 @@
"WebClientTourMobile2": "and easily controls other devices and Media Browser apps",
"MessageEnjoyYourStay": "Enjoy your stay",
"DashboardTourDashboard": "The server dashboard allows you to monitor your server and your users. You'll always know who is doing what and where they are.",
+ "DashboardTourHelp": "In-app help provides easy buttons to open wiki pages relating to the on-screen content.",
"DashboardTourUsers": "Easily create user accounts for your friends and family, each with their own permissions, library access, parental controls and more.",
"DashboardTourCinemaMode": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
"DashboardTourChapters": "Enable chapter image generation for your videos for a more pleasing presentation while viewing.",
@@ -642,7 +651,20 @@
"OptionLow": "Low",
"HeaderSettings": "Settings",
"OptionAutomaticallySyncNewContent": "Automatically sync new content",
- "OptionAutomaticallySyncNewContentHelp": "New content added to these folders will be automatically synced to the device.",
+ "OptionAutomaticallySyncNewContentHelp": "New content added to this category will be automatically synced to the device.",
"OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
- "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched."
+ "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
+ "LabelItemLimit": "Item limit:",
+ "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+ "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+ "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.",
+ "SyncJobItemStatusQueued": "Queued",
+ "SyncJobItemStatusConverting": "Converting",
+ "SyncJobItemStatusTransferring": "Transferring",
+ "SyncJobItemStatusSynced": "Synced",
+ "SyncJobItemStatusFailed": "Failed",
+ "SyncJobItemStatusRemovedFromDevice": "Removed from device",
+ "SyncJobItemStatusCancelled": "Cancelled",
+ "MessageJobItemHasNoActions": "d"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ar.json b/MediaBrowser.Server.Implementations/Localization/Server/ar.json
index 97302a0ff..9808ecb2c 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/ar.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/ar.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "\u0645\u064a\u062f\u064a\u0627 \u0628\u0631\u0627\u0648\u0632\u0631",
"ThisWizardWillGuideYou": "\u0645\u0631\u0634\u062f \u0627\u0644\u0627\u0639\u062f\u0627\u062f\u0627\u062a \u0633\u064a\u0633\u0627\u0639\u062f\u0643 \u062e\u0644\u0627\u0644 \u062e\u0637\u0648\u0627\u062a \u0639\u0645\u0644\u064a\u0629 \u0627\u0644\u0627\u0639\u062f\u0627\u062f\u0627\u062a. \u0644\u0644\u0628\u062f\u0621, \u0627\u0644\u0631\u062c\u0627\u0621 \u0627\u062e\u062a\u064a\u0627\u0631 \u0644\u063a\u062a\u0643 \u0627\u0644\u0645\u0641\u0636\u0644\u0629.",
"TellUsAboutYourself": "\u0627\u062e\u0628\u0631\u0646\u0627 \u0639\u0646 \u0646\u0641\u0633\u0643",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "\u0627\u0633\u0645\u0643 \u0627\u0644\u0627\u0648\u0644:",
"MoreUsersCanBeAddedLater": "\u0627\u0644\u0645\u0632\u064a\u062f \u0645\u0646 \u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645\u064a\u0646 \u064a\u0645\u0643\u0646 \u0627\u0636\u0627\u0641\u062a\u0647\u0645 \u0644\u0627\u062d\u0642\u0627 \u0645\u0646 \u0644\u0648\u062d\u0629 \u0627\u0644\u0627\u0639\u062f\u0627\u062f\u0627\u062a.",
"UserProfilesIntro": "\u0645\u064a\u062f\u064a\u0627 \u0628\u0631\u0627\u0648\u0632\u0631 \u0645\u062f\u0645\u062c \u0628\u0647 \u062f\u0639\u0645 \u0644\u0645\u0644\u0641\u0627\u062a \u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645\u064a\u0646, \u0648\u062a\u0645\u0643\u064a\u0646 \u0643\u0644 \u0645\u0633\u062a\u062e\u062f\u0645 \u0645\u0646 \u062d\u0635\u0648\u0644\u0647 \u0639\u0644\u0649 \u0627\u0639\u062f\u0627\u062f\u0627\u062a \u0627\u0644\u0639\u0631\u0636 \u0627\u0644\u062e\u0627\u0635\u0647 \u0628\u0647\u0645, \u0648\u0627\u0644\u0640 playstate \u0648\u0627\u0644\u0631\u0642\u0627\u0628\u0629 \u0627\u0644\u0627\u0628\u0648\u064a\u0629.",
@@ -37,10 +38,22 @@
"ButtonOk": "\u0645\u0648\u0627\u0641\u0642",
"ButtonCancel": "\u0627\u0644\u063a\u0627\u0621",
"ButtonNew": "New",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Movies",
+ "FolderTypeMusic": "Music",
+ "FolderTypeAdultVideos": "Adult videos",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Music videos",
+ "FolderTypeHomeVideos": "Home videos",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Books",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "\u0627\u0639\u062f\u0627\u062f \u0645\u0643\u062a\u0628\u0629 \u0627\u0644\u0648\u0633\u0627\u0626\u0637",
"ButtonAddMediaFolder": "\u0627\u0636\u0627\u0641\u0629 \u0645\u062c\u0644\u062f \u0644\u0644\u0648\u0633\u0627\u0626\u0637",
"LabelFolderType": "\u0646\u0648\u0639 \u0627\u0644\u0645\u062c\u0644\u062f:",
- "MediaFolderHelpPluginRequired": "* Requires the use of a plugin, e.g. GameBrowser or MB Bookshelf.",
"ReferToMediaLibraryWiki": "\u0627\u0644\u0631\u062c\u0648\u0639 \u0627\u0644\u0649 wiki \u0644\u0645\u0643\u062a\u0628\u0629 \u0627\u0644\u0648\u0633\u0627\u0626\u0637",
"LabelCountry": "\u0627\u0644\u0628\u0644\u062f:",
"LabelLanguage": "\u0627\u0644\u0644\u063a\u0629:",
@@ -52,12 +65,16 @@
"TabPreferences": "\u062a\u0641\u0636\u064a\u0644\u0627\u062a",
"TabPassword": "\u0643\u0644\u0645\u0629 \u0627\u0644\u0633\u0631",
"TabLibraryAccess": "\u0627\u0644\u062f\u062e\u0648\u0644 \u0627\u0644\u0649 \u0627\u0644\u0645\u0643\u062a\u0628\u0629",
+ "TabAccess": "Access",
"TabImage": "\u0635\u0648\u0631\u0629",
"TabProfile": "\u0633\u062c\u0644",
"TabMetadata": "Metadata",
"TabImages": "Images",
"TabNotifications": "Notifications",
"TabCollectionTitles": "Titles",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "Display missing episodes within seasons",
"LabelUnairedMissingEpisodesWithinSeasons": "Display unaired episodes within seasons",
"HeaderVideoPlaybackSettings": "\u0627\u0639\u062f\u0627\u062f\u0627\u062a \u062a\u0634\u063a\u064a\u0644 \u0627\u0644\u0641\u064a\u062f\u064a\u0648",
@@ -365,8 +382,8 @@
"LabelMaxScreenshotsPerItem": "Maximum number of screenshots per item:",
"LabelMinBackdropDownloadWidth": "Minimum backdrop download width:",
"LabelMinScreenshotDownloadWidth": "Minimum screenshot download width:",
- "ButtonAddScheduledTaskTrigger": "Add Task Trigger",
- "HeaderAddScheduledTaskTrigger": "Add Task Trigger",
+ "ButtonAddScheduledTaskTrigger": "Add Trigger",
+ "HeaderAddScheduledTaskTrigger": "Add Trigger",
"ButtonAdd": "Add",
"LabelTriggerType": "Trigger Type:",
"OptionDaily": "Daily",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "New Users",
"ButtonSignUp": "Sign up",
"ButtonForgotPassword": "Forgot password?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ca.json b/MediaBrowser.Server.Implementations/Localization/Server/ca.json
index 655cabcaf..4b0bb4453 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/ca.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/ca.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "This wizard will help guide you through the setup process. To begin, please select your preferred language.",
"TellUsAboutYourself": "Expliqui'ns sobre vost\u00e8",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "El seu nom:",
"MoreUsersCanBeAddedLater": "M\u00e9s usuaris es poden afegir m\u00e9s tard en el tauler d'instruments.",
"UserProfilesIntro": "Media Browser inclou suport integrat per als perfils d'usuari, la qual cosa permet que cada usuari tingui la seva pr\u00f2pia configuraci\u00f3 de pantalla, estat de reproducci\u00f3 i controls dels pares.",
@@ -37,10 +38,22 @@
"ButtonOk": "Ok",
"ButtonCancel": "Cancel",
"ButtonNew": "New",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Movies",
+ "FolderTypeMusic": "Music",
+ "FolderTypeAdultVideos": "Adult videos",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Music videos",
+ "FolderTypeHomeVideos": "Home videos",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Books",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "Setup your media library",
"ButtonAddMediaFolder": "Add media folder",
"LabelFolderType": "Folder type:",
- "MediaFolderHelpPluginRequired": "* Requires the use of a plugin, e.g. GameBrowser or MB Bookshelf.",
"ReferToMediaLibraryWiki": "Refer to the media library wiki.",
"LabelCountry": "Country:",
"LabelLanguage": "Language:",
@@ -52,12 +65,16 @@
"TabPreferences": "Preferences",
"TabPassword": "Password",
"TabLibraryAccess": "Library Access",
+ "TabAccess": "Access",
"TabImage": "Image",
"TabProfile": "Profile",
"TabMetadata": "Metadata",
"TabImages": "Images",
"TabNotifications": "Notifications",
"TabCollectionTitles": "Titles",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "Display missing episodes within seasons",
"LabelUnairedMissingEpisodesWithinSeasons": "Display unaired episodes within seasons",
"HeaderVideoPlaybackSettings": "Video Playback Settings",
@@ -365,8 +382,8 @@
"LabelMaxScreenshotsPerItem": "Maximum number of screenshots per item:",
"LabelMinBackdropDownloadWidth": "Minimum backdrop download width:",
"LabelMinScreenshotDownloadWidth": "Minimum screenshot download width:",
- "ButtonAddScheduledTaskTrigger": "Add Task Trigger",
- "HeaderAddScheduledTaskTrigger": "Add Task Trigger",
+ "ButtonAddScheduledTaskTrigger": "Add Trigger",
+ "HeaderAddScheduledTaskTrigger": "Add Trigger",
"ButtonAdd": "Add",
"LabelTriggerType": "Trigger Type:",
"OptionDaily": "Daily",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "New Users",
"ButtonSignUp": "Sign up",
"ButtonForgotPassword": "Forgot password?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/cs.json b/MediaBrowser.Server.Implementations/Localization/Server/cs.json
index 74aa0cd37..04f4b0e0f 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/cs.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/cs.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "Tento pr\u016fvodce V\u00e1m pom\u016f\u017ee proj\u00edt procesem nastaven\u00ed. Pro za\u010d\u00e1tek vyberte jazyk.",
"TellUsAboutYourself": "\u0158ekn\u011bte n\u00e1m n\u011bco o sob\u011b",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "Va\u0161e k\u0159estn\u00ed jm\u00e9no:",
"MoreUsersCanBeAddedLater": "Dal\u0161\u00ed u\u017eivatele m\u016f\u017eete p\u0159idat pozd\u011bji na n\u00e1st\u011bnce.",
"UserProfilesIntro": "Media Browser obsahuje zabudovanou podporu u\u017eivatelsk\u00fdch profil\u016f dovoluj\u00edc\u00ed ka\u017ed\u00e9mu u\u017eivateli konfigurovat nastaven\u00ed zobrazen\u00ed, p\u0159ehr\u00e1v\u00e1n\u00ed a rodi\u010dovskou kontrolu.",
@@ -37,10 +38,22 @@
"ButtonOk": "Ok",
"ButtonCancel": "Zru\u0161it",
"ButtonNew": "Nov\u00e9",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Movies",
+ "FolderTypeMusic": "Music",
+ "FolderTypeAdultVideos": "Adult videos",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Music videos",
+ "FolderTypeHomeVideos": "Home videos",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Books",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "Nastaven\u00ed Va\u0161i knihovny m\u00e9di\u00ed",
"ButtonAddMediaFolder": "P\u0159idat slo\u017eku m\u00e9di\u00ed",
"LabelFolderType": "Typ slo\u017eky:",
- "MediaFolderHelpPluginRequired": "* Vy\u017eaduje pou\u017eit\u00ed pluginu, nap\u0159. GameBrowser nebo MB Bookshelf",
"ReferToMediaLibraryWiki": "Pod\u00edvejte se na wiki knihovny m\u00e9di\u00ed.",
"LabelCountry": "Zem\u011b:",
"LabelLanguage": "Jazyk:",
@@ -52,12 +65,16 @@
"TabPreferences": "P\u0159edvolby",
"TabPassword": "Heslo",
"TabLibraryAccess": "P\u0159\u00edstup ke knihovn\u011b",
+ "TabAccess": "Access",
"TabImage": "Obr\u00e1zek",
"TabProfile": "Profil",
"TabMetadata": "Metadata",
"TabImages": "Obr\u00e1zky",
"TabNotifications": "Notifications",
"TabCollectionTitles": "N\u00e1zvy",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "Zobrazit chyb\u011bj\u00edc\u00ed epizody",
"LabelUnairedMissingEpisodesWithinSeasons": "Zobrazit neprov\u011btran\u00e9 epizody v r\u00e1mci sez\u00f3n",
"HeaderVideoPlaybackSettings": "Nastaven\u00ed p\u0159ehr\u00e1v\u00e1n\u00ed videa",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "New Users",
"ButtonSignUp": "Sign up",
"ButtonForgotPassword": "Forgot password?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/da.json b/MediaBrowser.Server.Implementations/Localization/Server/da.json
index db4c6d535..2b15e2989 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/da.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/da.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "Denne guide vil hj\u00e6lpe dig igennem ops\u00e6tningen. For at begynde, venligst v\u00e6lg dit fortrukne sprog.",
"TellUsAboutYourself": "Fort\u00e6l os lidt om dig selv",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "Dit fornavn",
"MoreUsersCanBeAddedLater": "Flere brugere kan tilf\u00f8jes senere i Betjeningspanelet.",
"UserProfilesIntro": "Media Browser inkludere indbygget underst\u00f8ttelse af bruger profiler, der giver den enkelte bruger mulighed for individuelle visningsindstillinger, Afspilningsstatus og for\u00e6ldre kontrol.",
@@ -37,10 +38,22 @@
"ButtonOk": "Ok",
"ButtonCancel": "Annuller",
"ButtonNew": "Ny",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Movies",
+ "FolderTypeMusic": "Music",
+ "FolderTypeAdultVideos": "Adult videos",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Music videos",
+ "FolderTypeHomeVideos": "Home videos",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Books",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "Konfigurer dit medie bibliotek",
"ButtonAddMediaFolder": "Tilf\u00f8j medie mappe",
"LabelFolderType": "Mappe type:",
- "MediaFolderHelpPluginRequired": "* Kr\u00e6ver brug af en tilf\u00f8jelse, fx GameBrowser eller MB Bookshelf.",
"ReferToMediaLibraryWiki": "Der henvises til medie bibliotekets wiki.",
"LabelCountry": "Land:",
"LabelLanguage": "Sprog:",
@@ -52,12 +65,16 @@
"TabPreferences": "Indstillinger",
"TabPassword": "Kode",
"TabLibraryAccess": "Bibliotek adgang",
+ "TabAccess": "Access",
"TabImage": "Billede",
"TabProfile": "Profil",
"TabMetadata": "Metadata",
"TabImages": "Billeder",
"TabNotifications": "Notifications",
"TabCollectionTitles": "Titler",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "Vis manglende episoder i s\u00e6soner",
"LabelUnairedMissingEpisodesWithinSeasons": "Vis endnu ikke sendte episoder i s\u00e6soner",
"HeaderVideoPlaybackSettings": "Video afspilnings indstillinger",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "New Users",
"ButtonSignUp": "Sign up",
"ButtonForgotPassword": "Forgot password?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/de.json b/MediaBrowser.Server.Implementations/Localization/Server/de.json
index c5e0715ff..eba61a04a 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/de.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/de.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "Dieser Assistent wird dich durch den Einrichtungsprozess f\u00fchren. Um zu beginnen, w\u00e4hle bitte deine bevorzugte Sprache.",
"TellUsAboutYourself": "Sag uns etwas \u00fcber dich selbst",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "Ihr Vorname:",
"MoreUsersCanBeAddedLater": "Weitere Benutzer k\u00f6nnen sp\u00e4ter \u00fcber die Optionsleiste hinzugef\u00fcgt werden.",
"UserProfilesIntro": "Media Browser verf\u00fcgt \u00fcber integrierte Benutzer Profile. Verwende diese Profile um Anzeigeeinstellungen, Abspielstatus sowie Kinder- und Jugendschutzverwaltungen pro Benutzer zu speichern und zu verwalten.",
@@ -37,10 +38,22 @@
"ButtonOk": "Ok",
"ButtonCancel": "Abbrechen",
"ButtonNew": "Neu",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Gemischte Inhalte",
+ "FolderTypeMovies": "Filme",
+ "FolderTypeMusic": "Musik",
+ "FolderTypeAdultVideos": "Videos f\u00fcr Erwachsene",
+ "FolderTypePhotos": "Fotos",
+ "FolderTypeMusicVideos": "Musikvideos",
+ "FolderTypeHomeVideos": "Heimvideos",
+ "FolderTypeGames": "Spiele",
+ "FolderTypeBooks": "B\u00fccher",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "\u00dcbernehmen",
+ "LabelContentType": "Inhalte-Typ:",
"HeaderSetupLibrary": "Medienbibliothek einrichten",
"ButtonAddMediaFolder": "Medienverzeichnis hinzuf\u00fcgen",
"LabelFolderType": "Verzeichnistyp:",
- "MediaFolderHelpPluginRequired": "* Ben\u00f6tigt ein Plugin, wie GameBrowser oder MB Bookshelf.",
"ReferToMediaLibraryWiki": "Siehe die Medienbibliothek Wiki",
"LabelCountry": "Land:",
"LabelLanguage": "Sprache:",
@@ -52,12 +65,16 @@
"TabPreferences": "Einstellungen",
"TabPassword": "Passwort",
"TabLibraryAccess": "Bibliothekenzugriff",
+ "TabAccess": "Zugang",
"TabImage": "Bild",
"TabProfile": "Profil",
"TabMetadata": "Metadata",
"TabImages": "Bilder",
"TabNotifications": "Benachrichtigungen",
"TabCollectionTitles": "Titel",
+ "HeaderDeviceAccess": "Ger\u00e4te Zugang",
+ "OptionEnableAccessFromAllDevices": "Zugriff von allen Ger\u00e4ten erlauben",
+ "DeviceAccessHelp": "Dies wird nur auf Ger\u00e4te angewandt die eindeutig identifiziert werden k\u00f6nnen und verhindert nicht den Web-Zugriff. Gefilterter Zugriff auf Ger\u00e4te verhindert die Nutzung neuer Ger\u00e4te solange, bis der Zugriff f\u00fcr diese freigegeben wird.",
"LabelDisplayMissingEpisodesWithinSeasons": "Zeige fehlende Episoden innerhalb von Staffeln",
"LabelUnairedMissingEpisodesWithinSeasons": "Zeige noch nicht ausgestahlte Episoden innerhalb von Staffeln",
"HeaderVideoPlaybackSettings": "Videowiedergabe Einstellungen",
@@ -365,8 +382,8 @@
"LabelMaxScreenshotsPerItem": "Maximale Anzahl von Screenshots pro Element:",
"LabelMinBackdropDownloadWidth": "Minimale Breite f\u00fcr zu herunterladende Hintergr\u00fcnde:",
"LabelMinScreenshotDownloadWidth": "Minimale Breite f\u00fcr zu herunterladende Screenshot:",
- "ButtonAddScheduledTaskTrigger": "F\u00fcge Task Ausl\u00f6ser hinzu",
- "HeaderAddScheduledTaskTrigger": "F\u00fcge Task Ausl\u00f6ser hinzu",
+ "ButtonAddScheduledTaskTrigger": "Ausl\u00f6ser hinzuf\u00fcgen",
+ "HeaderAddScheduledTaskTrigger": "Ausl\u00f6ser hinzuf\u00fcgen",
"ButtonAdd": "Hinzuf\u00fcgen",
"LabelTriggerType": "Ausl\u00f6ser Typ:",
"OptionDaily": "T\u00e4glich",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Rolle",
"OptionPlayUnwatchedTrailersOnly": "Spiele nur bisher nicht gesehene Trailer",
"HeaderTrailerReelHelp": "Starte eine Trailer Rolle, um dir eine lang andauernde Playlist mit Trailern anzuschauen.",
- "MessageNoTrailersFound": "Keine Trailer gefunden. Installiere das Trailer Channel Plugin, um eine Bibliothek aus Trailern vom Internet zu importieren.",
+ "MessageNoTrailersFound": "Keine Trailer gefunden. Installieren Sie den Trailer-Channel um Ihre Film-Bibliothek mit Trailer aus dem Internet zu erweitern.",
"HeaderNewUsers": "Neue Benutzer",
"ButtonSignUp": "Anmeldung",
"ButtonForgotPassword": "Passwort vergessen?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Begrenze auf ein eingebundenes Bild",
"LabelEnableSingleImageInDidlLimitHelp": "Einige Ger\u00e4te zeigen m\u00f6glicherweise Darstellungsfehler wenn mehrere Bilder mit Didl eingebunden wurden.",
"TabActivity": "Aktivit\u00e4t",
- "TitleSync": "Synchronisation"
+ "TitleSync": "Synchronisation",
+ "OptionAllowSyncContent": "Erlaube das Synchronisieren zu Ger\u00e4ten.",
+ "NameSeasonUnknown": "Staffel unbekannt",
+ "NameSeasonNumber": "Staffel {0}",
+ "LabelNewUserNameHelp": "Benutzernamen k\u00f6nnen Zeichen (a-z), Zahlen (0-9), Striche (-), Unterstriche (_), Apostrophe (') und Punkte (.) enthalten.",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/el.json b/MediaBrowser.Server.Implementations/Localization/Server/el.json
index 41239b912..a3889792b 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/el.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/el.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "\u0391\u03c5\u03c4\u03cc\u03c2 \u03bf \u03bf\u03b4\u03b7\u03b3\u03cc\u03c2 \u03b8\u03b1 \u03c3\u03b1\u03c2 \u03ba\u03b1\u03b8\u03bf\u03b4\u03b7\u03b3\u03ae\u03c3\u03b5\u03b9 \u03bc\u03ad\u03c3\u03c9 \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03b9\u03ba\u03b1\u03c3\u03af\u03b1\u03c2 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5, \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03b3\u03bb\u03ce\u03c3\u03c3\u03b1 \u03c4\u03b7\u03c2 \u03c0\u03c1\u03bf\u03c4\u03af\u03bc\u03b7\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2.",
"TellUsAboutYourself": "\u03a0\u03b5\u03af\u03c4\u03b5 \u03bc\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03b5\u03c3\u03ac\u03c2",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "\u03a4\u03bf \u03cc\u03bd\u03bf\u03bc\u03ac \u03c3\u03b1\u03c2",
"MoreUsersCanBeAddedLater": "\u03a0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03bf\u03c5\u03c2 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b5\u03c2 \u03bc\u03c0\u03bf\u03c1\u03bf\u03cd\u03bd \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c4\u03b5\u03b8\u03bf\u03cd\u03bd \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1 \u03bc\u03b5 \u03c4\u03bf \u03c4\u03b1\u03bc\u03c0\u03bb\u03cc",
"UserProfilesIntro": "Media Browser \u03c0\u03b5\u03c1\u03b9\u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03b5\u03b9 \u03b5\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03bc\u03ad\u03bd\u03b7 \u03c5\u03c0\u03bf\u03c3\u03c4\u03ae\u03c1\u03b9\u03be\u03b7 \u03b3\u03b9\u03b1 \u03c0\u03c1\u03bf\u03c6\u03af\u03bb \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7, \u03c0\u03bf\u03c5 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03bf\u03c5\u03bd \u03c3\u03b5 \u03ba\u03ac\u03b8\u03b5 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03bd\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03c4\u03b9\u03c2 \u03b4\u03b9\u03ba\u03ad\u03c2 \u03c4\u03bf\u03c5\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03bf\u03b8\u03cc\u03bd\u03b7\u03c2, playstate \u03ba\u03b1\u03b9 \u03b3\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5.\n",
@@ -37,10 +38,22 @@
"ButtonOk": "\u03b5\u03bd\u03c4\u03ac\u03be\u03b5\u03b9",
"ButtonCancel": "\u0391\u03ba\u03cd\u03c1\u03c9\u03c3\u03b7 ",
"ButtonNew": "New",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Movies",
+ "FolderTypeMusic": "Music",
+ "FolderTypeAdultVideos": "Adult videos",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Music videos",
+ "FolderTypeHomeVideos": "Home videos",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Books",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "Setup your media library",
"ButtonAddMediaFolder": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c6\u03ac\u03ba\u03b5\u03bb\u03bf \u03c4\u03bf\u03c5 Media",
"LabelFolderType": "\u03a4\u03cd\u03c0\u03bf \u03c6\u03b1\u03ba\u03ad\u03bb\u03bf\u03c5 ",
- "MediaFolderHelpPluginRequired": "\u0391\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03c4\u03b7 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03b5\u03bd\u03cc\u03c2 plugin.\u03c0.\u03c7. Gamebrowser \u03ae MB Bookshelf.",
"ReferToMediaLibraryWiki": "\u0391\u03bd\u03b1\u03c4\u03c1\u03b5\u03be\u03c4\u03b5 \u03c3\u03c4\u03bf media \u03b2\u03b9\u03b2\u03bb\u03b9\u03bf\u03b8\u03ae\u03ba\u03b7 wiki",
"LabelCountry": "T\u03b7 \u03c7\u03ce\u03c1\u03b1",
"LabelLanguage": "\u03a4\u03b7 \u03b3\u03bb\u03ce\u03c3\u03c3\u03b1",
@@ -52,12 +65,16 @@
"TabPreferences": "\u03a0\u03c1\u03bf\u03c4\u03b9\u03bc\u03ae\u03c3\u03b5\u03b9\u03c2 ",
"TabPassword": "\u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc",
"TabLibraryAccess": "\u03a0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03b2\u03b9\u03b2\u03bb\u03b9\u03bf\u03b8\u03ae\u03ba\u03b7",
+ "TabAccess": "Access",
"TabImage": "\u03b5\u03b9\u03ba\u03cc\u03bd\u03b1",
"TabProfile": "\u03c0\u03c1\u03bf\u03c6\u03af\u03bb ",
"TabMetadata": "Metadata",
"TabImages": "Images",
"TabNotifications": "Notifications",
"TabCollectionTitles": "Titles",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "\u03b4\u03b5\u03af\u03c7\u03bd\u03bf\u03c5\u03bd \u03bb\u03b5\u03af\u03c0\u03b5\u03b9 \u03b5\u03c0\u03b5\u03b9\u03c3\u03cc\u03b4\u03b9\u03b1 \u03b5\u03bd\u03c4\u03cc\u03c2 \u03b5\u03c0\u03bf\u03c7\u03ad\u03c2",
"LabelUnairedMissingEpisodesWithinSeasons": "\u03b4\u03b5\u03af\u03c7\u03bd\u03bf\u03c5\u03bd unaired \u03b5\u03c0\u03b5\u03b9\u03c3\u03cc\u03b4\u03b9\u03b1 \u03b5\u03bd\u03c4\u03cc\u03c2 \u03b5\u03c0\u03bf\u03c7\u03ad\u03c2",
"HeaderVideoPlaybackSettings": "\u0391\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae \u03b2\u03af\u03bd\u03c4\u03b5\u03bf \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2.",
@@ -365,8 +382,8 @@
"LabelMaxScreenshotsPerItem": "Maximum number of screenshots per item:",
"LabelMinBackdropDownloadWidth": "Minimum backdrop download width:",
"LabelMinScreenshotDownloadWidth": "Minimum screenshot download width:",
- "ButtonAddScheduledTaskTrigger": "Add Task Trigger",
- "HeaderAddScheduledTaskTrigger": "Add Task Trigger",
+ "ButtonAddScheduledTaskTrigger": "Add Trigger",
+ "HeaderAddScheduledTaskTrigger": "Add Trigger",
"ButtonAdd": "Add",
"LabelTriggerType": "Trigger Type:",
"OptionDaily": "Daily",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "New Users",
"ButtonSignUp": "Sign up",
"ButtonForgotPassword": "Forgot password?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json b/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json
index 9eeac0f85..ec4a4b452 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "This wizard will help guide you through the setup process. To begin, please select your preferred language.",
"TellUsAboutYourself": "Tell us about yourself",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "Your first name:",
"MoreUsersCanBeAddedLater": "More users can be added later within the Dashboard.",
"UserProfilesIntro": "Media Browser includes built-in support for user profiles, enabling each user to have their own display settings, playstate and parental controls.",
@@ -37,10 +38,22 @@
"ButtonOk": "Ok",
"ButtonCancel": "Cancel",
"ButtonNew": "New",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Movies",
+ "FolderTypeMusic": "Music",
+ "FolderTypeAdultVideos": "Adult videos",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Music videos",
+ "FolderTypeHomeVideos": "Home videos",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Books",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "Setup your media library",
"ButtonAddMediaFolder": "Add media folder",
"LabelFolderType": "Folder type:",
- "MediaFolderHelpPluginRequired": "* Requires the use of a plugin, e.g. GameBrowser or MB Bookshelf.",
"ReferToMediaLibraryWiki": "Refer to the media library wiki.",
"LabelCountry": "Country:",
"LabelLanguage": "Language:",
@@ -52,12 +65,16 @@
"TabPreferences": "Preferences",
"TabPassword": "Password",
"TabLibraryAccess": "Library Access",
+ "TabAccess": "Access",
"TabImage": "Image",
"TabProfile": "Profile",
"TabMetadata": "Metadata",
"TabImages": "Images",
"TabNotifications": "Notifications",
"TabCollectionTitles": "Titles",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "Display missing episodes within seasons",
"LabelUnairedMissingEpisodesWithinSeasons": "Display unaired episodes within seasons",
"HeaderVideoPlaybackSettings": "Video Playback Settings",
@@ -365,8 +382,8 @@
"LabelMaxScreenshotsPerItem": "Maximum number of screenshots per item:",
"LabelMinBackdropDownloadWidth": "Minimum backdrop download width:",
"LabelMinScreenshotDownloadWidth": "Minimum screenshot download width:",
- "ButtonAddScheduledTaskTrigger": "Add Task Trigger",
- "HeaderAddScheduledTaskTrigger": "Add Task Trigger",
+ "ButtonAddScheduledTaskTrigger": "Add Trigger",
+ "HeaderAddScheduledTaskTrigger": "Add Trigger",
"ButtonAdd": "Add",
"LabelTriggerType": "Trigger Type:",
"OptionDaily": "Daily",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "New Users",
"ButtonSignUp": "Sign up",
"ButtonForgotPassword": "Forgot password?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/en_US.json b/MediaBrowser.Server.Implementations/Localization/Server/en_US.json
index 9cb4fb6c5..af47501eb 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/en_US.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/en_US.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "This wizard will help guide you through the setup process. To begin, please select your preferred language.",
"TellUsAboutYourself": "Tell us about yourself",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "Your first name:",
"MoreUsersCanBeAddedLater": "More users can be added later within the Dashboard.",
"UserProfilesIntro": "Media Browser includes built-in support for user profiles, enabling each user to have their own display settings, playstate and parental controls.",
@@ -37,10 +38,22 @@
"ButtonOk": "Ok",
"ButtonCancel": "Cancel",
"ButtonNew": "New",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Movies",
+ "FolderTypeMusic": "Music",
+ "FolderTypeAdultVideos": "Adult videos",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Music videos",
+ "FolderTypeHomeVideos": "Home videos",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Books",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "Setup your media library",
"ButtonAddMediaFolder": "Add media folder",
"LabelFolderType": "Folder type:",
- "MediaFolderHelpPluginRequired": "* Requires the use of a plugin, e.g. GameBrowser or MB Bookshelf.",
"ReferToMediaLibraryWiki": "Refer to the media library wiki.",
"LabelCountry": "Country:",
"LabelLanguage": "Language:",
@@ -52,12 +65,16 @@
"TabPreferences": "Preferences",
"TabPassword": "Password",
"TabLibraryAccess": "Library Access",
+ "TabAccess": "Access",
"TabImage": "Image",
"TabProfile": "Profile",
"TabMetadata": "Metadata",
"TabImages": "Images",
"TabNotifications": "Notifications",
"TabCollectionTitles": "Titles",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "Display missing episodes within seasons",
"LabelUnairedMissingEpisodesWithinSeasons": "Display unaired episodes within seasons",
"HeaderVideoPlaybackSettings": "Video Playback Settings",
@@ -365,8 +382,8 @@
"LabelMaxScreenshotsPerItem": "Maximum number of screenshots per item:",
"LabelMinBackdropDownloadWidth": "Minimum backdrop download width:",
"LabelMinScreenshotDownloadWidth": "Minimum screenshot download width:",
- "ButtonAddScheduledTaskTrigger": "Add Task Trigger",
- "HeaderAddScheduledTaskTrigger": "Add Task Trigger",
+ "ButtonAddScheduledTaskTrigger": "Add Trigger",
+ "HeaderAddScheduledTaskTrigger": "Add Trigger",
"ButtonAdd": "Add",
"LabelTriggerType": "Trigger Type:",
"OptionDaily": "Daily",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "New Users",
"ButtonSignUp": "Sign up",
"ButtonForgotPassword": "Forgot password?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/es.json b/MediaBrowser.Server.Implementations/Localization/Server/es.json
index a720a1510..844d3e8c7 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/es.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/es.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "Este asistente lo guiar\u00e1 por el proceso de instalaci\u00f3n. Para comenzar seleccione su idioma preferido.",
"TellUsAboutYourself": "D\u00edganos acerca de usted",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "Su nombre:",
"MoreUsersCanBeAddedLater": "M\u00e1s usuarios pueden agregarse m\u00e1s tarde en el panel de control.",
"UserProfilesIntro": "Media Browser incluye soporte integrado para los perfiles de usuario, lo que permite que cada usuario tenga su propia configuraci\u00f3n de la pantalla, estado de reproducci\u00f3n y control parental.",
@@ -37,10 +38,22 @@
"ButtonOk": "OK",
"ButtonCancel": "Cancelar",
"ButtonNew": "Nuevo",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Movies",
+ "FolderTypeMusic": "Music",
+ "FolderTypeAdultVideos": "Adult videos",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Music videos",
+ "FolderTypeHomeVideos": "Home videos",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Books",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "Configurar biblioteca de medios",
"ButtonAddMediaFolder": "Agregar una carpeta de medios",
"LabelFolderType": "Tipo de carpeta:",
- "MediaFolderHelpPluginRequired": "* Requiere el uso de un plugin, por ejemplo GameBrowser o MB Bookshelf",
"ReferToMediaLibraryWiki": "Consultar el wiki de la biblioteca de medios",
"LabelCountry": "Pa\u00eds:",
"LabelLanguage": "Idioma:",
@@ -52,12 +65,16 @@
"TabPreferences": "Preferencias",
"TabPassword": "Contrase\u00f1a",
"TabLibraryAccess": "Acceso a biblioteca",
+ "TabAccess": "Access",
"TabImage": "imagen",
"TabProfile": "Perfil",
"TabMetadata": "Metadata",
"TabImages": "Im\u00e1genes",
"TabNotifications": "Notificaciones",
"TabCollectionTitles": "T\u00edtulos",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "Mostar episodios no disponibles en temporadas",
"LabelUnairedMissingEpisodesWithinSeasons": "Mostrar episodios a\u00fan no emitidos en temporadas",
"HeaderVideoPlaybackSettings": "Ajustes de Reproducci\u00f3n de Video",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "New Users",
"ButtonSignUp": "Sign up",
"ButtonForgotPassword": "Forgot password?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json b/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json
index 7c532182b..880544cf6 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "Este asistente le guiar\u00e1 a trav\u00e9s del proceso de instalaci\u00f3n. Para comenzar, por favor seleccione su lenguaje preferido.",
"TellUsAboutYourself": "D\u00edganos sobre usted",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "Su nombre:",
"MoreUsersCanBeAddedLater": "Se pueden agregar m\u00e1s usuarios posteriormente en el Panel de Control.",
"UserProfilesIntro": "Media Browser incluye soporte integrado para perfiles de usuario, permiti\u00e9ndo a cada usuario tener su propia configuraci\u00f3n de pantalla, estado de reproducci\u00f3n y controles parentales.",
@@ -37,10 +38,22 @@
"ButtonOk": "Ok",
"ButtonCancel": "Cancelar",
"ButtonNew": "Nuevo",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Contenido mezclado",
+ "FolderTypeMovies": "Pel\u00edculas",
+ "FolderTypeMusic": "M\u00fasica",
+ "FolderTypeAdultVideos": "Videos para adultos",
+ "FolderTypePhotos": "Fotos",
+ "FolderTypeMusicVideos": "Videos musicales",
+ "FolderTypeHomeVideos": "Videos caseros",
+ "FolderTypeGames": "Juegos",
+ "FolderTypeBooks": "Libros",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Heredar",
+ "LabelContentType": "Tipo de Contenido:",
"HeaderSetupLibrary": "Configurar su biblioteca de medios",
"ButtonAddMediaFolder": "Agregar carpeta de medios",
"LabelFolderType": "Tipo de carpeta:",
- "MediaFolderHelpPluginRequired": "* Requiere el uso de un complemento, p. ej. GameBrowser o MB Bookshelf.",
"ReferToMediaLibraryWiki": "Consultar la wiki de la biblioteca de medios.",
"LabelCountry": "Pa\u00eds:",
"LabelLanguage": "Idioma:",
@@ -52,12 +65,16 @@
"TabPreferences": "Preferencias",
"TabPassword": "Contrase\u00f1a",
"TabLibraryAccess": "Acceso a biblioteca",
+ "TabAccess": "Acceso",
"TabImage": "Imagen",
"TabProfile": "Perf\u00edl",
"TabMetadata": "Metadatos",
"TabImages": "Im\u00e1genes",
"TabNotifications": "Notificaciones",
"TabCollectionTitles": "T\u00edtulos",
+ "HeaderDeviceAccess": "Acceso a Dispositivos",
+ "OptionEnableAccessFromAllDevices": "Habilitar acceso desde todos los dispositivos",
+ "DeviceAccessHelp": "Esto solo aplica a dispositivos que pueden ser identificados de manera individual y no evitar\u00e1 acceso al navegador. Al filtrar el acceso de usuarios a dispositivos se impedir\u00e1 que utilicen nuevos dispositivos hasta que hayan sido aprobados aqu\u00ed.",
"LabelDisplayMissingEpisodesWithinSeasons": "Mostar episodios no disponibles en las temporadas",
"LabelUnairedMissingEpisodesWithinSeasons": "Mostrar episodios a\u00fan no emitidos en las temporadas",
"HeaderVideoPlaybackSettings": "Ajustes de Reproducci\u00f3n de Video",
@@ -232,10 +249,10 @@
"HeaderFeatureAccess": "Permisos de acceso",
"OptionAllowMediaPlayback": "Permitir reproducci\u00f3n de medios",
"OptionAllowBrowsingLiveTv": "Permitir acceder a TV en vivo",
- "OptionAllowDeleteLibraryContent": "Allow deletion of library content",
+ "OptionAllowDeleteLibraryContent": "Permitir eliminar contenido de la biblioteca",
"OptionAllowManageLiveTv": "Permitir administrar grabaciones de TV en vivo",
- "OptionAllowRemoteControlOthers": "Allow remote control of other users",
- "OptionAllowRemoteSharedDevices": "Allow remote control of shared devices",
+ "OptionAllowRemoteControlOthers": "Permitir control remoto de otros usuarios",
+ "OptionAllowRemoteSharedDevices": "Permitir control remoto de dispositivos compartidos",
"OptionAllowRemoteSharedDevicesHelp": "Los dispositivos dnla son considerados como compartidos hasta que alg\u00fan usuario comienza a controlarlo.",
"HeaderRemoteControl": "Control Remoto",
"OptionMissingTmdbId": "Falta Id de Tmdb",
@@ -365,8 +382,8 @@
"LabelMaxScreenshotsPerItem": "N\u00famero m\u00e1ximo de capturas de pantalla por \u00edtem:",
"LabelMinBackdropDownloadWidth": "Anchura m\u00ednima de descarga de im\u00e1genes de fondo:",
"LabelMinScreenshotDownloadWidth": "Anchura m\u00ednima de descarga de capturas de pantalla:",
- "ButtonAddScheduledTaskTrigger": "Agregar Disparador de Tarea",
- "HeaderAddScheduledTaskTrigger": "Agregar Disparador de Tarea",
+ "ButtonAddScheduledTaskTrigger": "Agregar Disparador",
+ "HeaderAddScheduledTaskTrigger": "Agregar Disparador",
"ButtonAdd": "Agregar",
"LabelTriggerType": "Tipo de Evento:",
"OptionDaily": "Diario",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Carrete de Avances",
"OptionPlayUnwatchedTrailersOnly": "Reproducir \u00fanicamente avances no vistos",
"HeaderTrailerReelHelp": "Iniciar un carrete de avances para reproducir una lista de reproducci\u00f3n de larga duraci\u00f3n de avances.",
- "MessageNoTrailersFound": "No se encontraron avances. Instalar el complemento \"Canal trailers\" para importar una biblioteca de avances de internet.",
+ "MessageNoTrailersFound": "No se encontraron avances. Instale el canal de avances para mejorar su experiencia con pel\u00edculas al agregar una biblioteca de avances desde el Internet.",
"HeaderNewUsers": "Nuevos Usuarios",
"ButtonSignUp": "Registrarse",
"ButtonForgotPassword": "\u00bfOlvidaste la contrase\u00f1a?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limitar a una sola imagen incrustada.",
"LabelEnableSingleImageInDidlLimitHelp": "Algunos dispositivos no renderisaran apropiadamente si hay m\u00faltiples im\u00e1genes incrustadas en el Didl",
"TabActivity": "Actividad",
- "TitleSync": "Sinc"
+ "TitleSync": "Sinc",
+ "OptionAllowSyncContent": "Permitir sincronizaci\u00f3n de medios con dispositivos",
+ "NameSeasonUnknown": "Temporada Desconocida",
+ "NameSeasonNumber": "Temporada {0}",
+ "LabelNewUserNameHelp": "Los nombres de usuario pueden contener letras (a-z), n\u00fameros (0-9), guiones (-), guiones bajos (_) y puntos (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/fi.json b/MediaBrowser.Server.Implementations/Localization/Server/fi.json
index c2c6c8612..4c84eb4b8 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/fi.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/fi.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "T\u00e4m\u00e4 ty\u00f6kalu auttaa sinua asennus prosessin aikana. loittaaksesi valitse kieli.",
"TellUsAboutYourself": "Kerro meille itsest\u00e4si",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "Sinun ensimm\u00e4inen nimi:",
"MoreUsersCanBeAddedLater": "K\u00e4ytt\u00e4ji\u00e4 voi lis\u00e4t\u00e4 lis\u00e4\u00e4 my\u00f6hemmin Dashboardista",
"UserProfilesIntro": "Media Browser sis\u00e4lt\u00e4\u00e4 sis\u00e4\u00e4nrakenntun tuen k\u00e4ytt\u00e4j\u00e4 profiileille, omat asetukset jokaiselle, paystate ja rinnakkaisen hallinnan.",
@@ -37,10 +38,22 @@
"ButtonOk": "Ok",
"ButtonCancel": "Lopeta",
"ButtonNew": "New",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Movies",
+ "FolderTypeMusic": "Music",
+ "FolderTypeAdultVideos": "Adult videos",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Music videos",
+ "FolderTypeHomeVideos": "Home videos",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Books",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "Aseta sinun media kirjasto",
"ButtonAddMediaFolder": "Lis\u00e4\u00e4 media kansio",
"LabelFolderType": "Kansion tyyppi:",
- "MediaFolderHelpPluginRequired": "* Vaatii lis\u00e4osan, kuten GameBrowser tai MB Bookshelf.",
"ReferToMediaLibraryWiki": "Viittus media kirjaston wikiin.",
"LabelCountry": "Maa:",
"LabelLanguage": "Kieli:",
@@ -52,12 +65,16 @@
"TabPreferences": "Asetukset",
"TabPassword": "Salasana",
"TabLibraryAccess": "Kirjaston P\u00e4\u00e4sy",
+ "TabAccess": "Access",
"TabImage": "Kuva",
"TabProfile": "Profiili",
"TabMetadata": "Metadata",
"TabImages": "Images",
"TabNotifications": "Notifications",
"TabCollectionTitles": "Titles",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "N\u00e4yt\u00e4 puuttuvat jaksot tuotantokausissa",
"LabelUnairedMissingEpisodesWithinSeasons": "N\u00e4yt\u00e4 julkaisemattomat jaksot tuotantokausissa",
"HeaderVideoPlaybackSettings": "Videon Toistamisen Asetukset",
@@ -365,8 +382,8 @@
"LabelMaxScreenshotsPerItem": "Maximum number of screenshots per item:",
"LabelMinBackdropDownloadWidth": "Minimum backdrop download width:",
"LabelMinScreenshotDownloadWidth": "Minimum screenshot download width:",
- "ButtonAddScheduledTaskTrigger": "Add Task Trigger",
- "HeaderAddScheduledTaskTrigger": "Add Task Trigger",
+ "ButtonAddScheduledTaskTrigger": "Add Trigger",
+ "HeaderAddScheduledTaskTrigger": "Add Trigger",
"ButtonAdd": "Add",
"LabelTriggerType": "Trigger Type:",
"OptionDaily": "Daily",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "New Users",
"ButtonSignUp": "Sign up",
"ButtonForgotPassword": "Forgot password?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/fr.json b/MediaBrowser.Server.Implementations/Localization/Server/fr.json
index b3f6ead82..de92b5b0d 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/fr.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/fr.json
@@ -15,10 +15,11 @@
"LabelFinish": "Terminer",
"LabelNext": "Suivant",
"LabelYoureDone": "Vous avez Termin\u00e9!",
- "WelcomeToMediaBrowser": "Bienvenue \u00e0 Media Browser!",
+ "WelcomeToMediaBrowser": "Bienvenue sur Media Browser!",
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "Cet assistant vous guidera dans le processus de configuration. Pour commencer, merci de s\u00e9lectionner votre langue pr\u00e9f\u00e9r\u00e9e.",
"TellUsAboutYourself": "Parlez-nous de vous",
+ "ButtonQuickStartGuide": "Guide de d\u00e9marrage rapide",
"LabelYourFirstName": "Votre pr\u00e9nom:",
"MoreUsersCanBeAddedLater": "D'autres utilisateurs pourront \u00eatre ajout\u00e9s ult\u00e9rieurement \u00e0 partir du tableau de bord.",
"UserProfilesIntro": "Media Browser supporte nativement les profils utilisateurs, donnant la possibilit\u00e9 pour chaque utilisateur d'avoir ses propres param\u00e8tres d'affichage, \u00e9tats de lecture et param\u00e8tres de contr\u00f4le parental.",
@@ -37,10 +38,22 @@
"ButtonOk": "Ok",
"ButtonCancel": "Annuler",
"ButtonNew": "Nouveau",
+ "HeaderSyncJobInfo": "T\u00e2che de synchronisation",
+ "FolderTypeMixed": "Contenus m\u00e9lang\u00e9s",
+ "FolderTypeMovies": "Films",
+ "FolderTypeMusic": "Musique",
+ "FolderTypeAdultVideos": "Vid\u00e9os Adultes",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Vid\u00e9os Musical",
+ "FolderTypeHomeVideos": "Vid\u00e9os personnelles",
+ "FolderTypeGames": "Jeux",
+ "FolderTypeBooks": "Livres",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "H\u00e9rite",
+ "LabelContentType": "Type de contenu :",
"HeaderSetupLibrary": "Configurer votre biblioth\u00e8que de m\u00e9dia",
"ButtonAddMediaFolder": "Ajouter un r\u00e9pertoire de m\u00e9dia",
"LabelFolderType": "Type de r\u00e9pertoire:",
- "MediaFolderHelpPluginRequired": "* N\u00e9cessite l'utilisation d'un plugin, par exemple : \"GameBrowser\" ou \"MB BookShelf\".",
"ReferToMediaLibraryWiki": "Se r\u00e9f\u00e9rer au wiki des biblioth\u00e8ques de m\u00e9dia",
"LabelCountry": "Pays:",
"LabelLanguage": "Langue:",
@@ -52,12 +65,16 @@
"TabPreferences": "Pr\u00e9f\u00e9rences",
"TabPassword": "Mot de passe",
"TabLibraryAccess": "Acc\u00e8s aux biblioth\u00e8ques",
+ "TabAccess": "Acc\u00e8s",
"TabImage": "Image",
"TabProfile": "Profil",
"TabMetadata": "M\u00e9tadonn\u00e9es",
"TabImages": "Images",
"TabNotifications": "Notifications",
"TabCollectionTitles": "Titres",
+ "HeaderDeviceAccess": "Acc\u00e8s \u00e0 l'appareil",
+ "OptionEnableAccessFromAllDevices": "Autoriser l'acc\u00e8s \u00e0 tous les appareils",
+ "DeviceAccessHelp": "Ceci ne s'applique qu'aux appareils qui peuvent \u00eatre identifi\u00e9s de mani\u00e8re unique et qui n'emp\u00eachent pas l'acc\u00e8s au navigateur. Le filtrage de l'acc\u00e8s aux appareil par utilisateur emp\u00eachera l'utilisation de nouveaux appareils jusqu'\u00e0 ce qu'ils soient approuv\u00e9s ici.",
"LabelDisplayMissingEpisodesWithinSeasons": "Afficher les \u00e9pisodes manquants dans les saisons",
"LabelUnairedMissingEpisodesWithinSeasons": "Afficher les \u00e9pisodes non diffus\u00e9s dans les saisons",
"HeaderVideoPlaybackSettings": "Param\u00e8tres de lecture video",
@@ -232,10 +249,10 @@
"HeaderFeatureAccess": "Acc\u00e8s aux caract\u00e9ristiques",
"OptionAllowMediaPlayback": "Autoriser la lecture du m\u00e9dia",
"OptionAllowBrowsingLiveTv": "Autoriser la TV en direct",
- "OptionAllowDeleteLibraryContent": "Allow deletion of library content",
+ "OptionAllowDeleteLibraryContent": "Autoriser la suppression de contenu dans la biblioth\u00e8que",
"OptionAllowManageLiveTv": "Autoriser la gestion des enregistrements de la TV en direct",
- "OptionAllowRemoteControlOthers": "Allow remote control of other users",
- "OptionAllowRemoteSharedDevices": "Allow remote control of shared devices",
+ "OptionAllowRemoteControlOthers": "Autoriser le contr\u00f4le \u00e0 distance d'autres utilisateurs",
+ "OptionAllowRemoteSharedDevices": "Autoriser le contr\u00f4le \u00e0 distance des disques partag\u00e9s",
"OptionAllowRemoteSharedDevicesHelp": "Les p\u00e9riph\u00e9riques Dlna sont consid\u00e9r\u00e9s comme partag\u00e9s tant qu'un utilisateur ne commence pas \u00e0 le contr\u00f4ler.",
"HeaderRemoteControl": "Contr\u00f4le \u00e0 distance",
"OptionMissingTmdbId": "ID TMDb manquant",
@@ -450,7 +467,7 @@
"CustomDlnaProfilesHelp": "Cr\u00e9er un profil personnalis\u00e9 pour cibler un nouveau p\u00e9riph\u00e9rique ou surpasser un profil syst\u00e8me.",
"SystemDlnaProfilesHelp": "Les profils syst\u00e8mes sont en lecture seule. Les modifications apport\u00e9es \u00e0 un profil syst\u00e8me seront enregistr\u00e9es sous un nouveau profil personnalis\u00e9.",
"TitleDashboard": "Tableau de bord",
- "TabHome": "Portail",
+ "TabHome": "Accueil",
"TabInfo": "Info",
"HeaderLinks": "Liens",
"HeaderSystemPaths": "Chemins d'acc\u00e8s syst\u00e8mes",
@@ -529,7 +546,7 @@
"LabelDeleteEmptyFoldersHelp": "Activer cette option pour garder le r\u00e9pertoire de t\u00e9l\u00e9chargement vide.",
"LabelDeleteLeftOverFiles": "Supprimer le reste des fichiers avec les extensions suivantes:",
"LabelDeleteLeftOverFilesHelp": "S\u00e9parer par ;. Par exemple: .nfo;.txt",
- "OptionOverwriteExistingEpisodes": "\u00c9craser les \u00e9pisodes existantes",
+ "OptionOverwriteExistingEpisodes": "\u00c9craser les \u00e9pisodes existants",
"LabelTransferMethod": "M\u00e9thode de transfert",
"OptionCopy": "Copier",
"OptionMove": "D\u00e9placer",
@@ -657,7 +674,7 @@
"NotificationOptionPluginError": "\u00c9chec de plugin",
"ButtonVolumeUp": "Volume haut",
"ButtonVolumeDown": "Volume bas",
- "ButtonMute": "Sourdine",
+ "ButtonMute": "Muet",
"HeaderLatestMedia": "Derniers m\u00e9dias",
"OptionSpecialFeatures": "Bonus",
"HeaderCollections": "Collections",
@@ -1214,7 +1231,7 @@
"TabCameraUpload": "Upload de la cam\u00e9ra",
"TabDevices": "P\u00e9riph\u00e9riques",
"HeaderCameraUploadHelp": "Uploader automatiquement les photos et les vid\u00e9os depuis vos p\u00e9riph\u00e9riques mobiles dans Media Browser.",
- "MessageNoDevicesSupportCameraUpload": "Vous n'avez actuellement aucun p\u00e9riph\u00e9riques support\u00e9 par l'upload de la cam\u00e9ra.",
+ "MessageNoDevicesSupportCameraUpload": "Vous n'avez actuellement aucun p\u00e9riph\u00e9riques supportant l'upload du flux vid\u00e9o de la cam\u00e9ra.",
"LabelCameraUploadPath": "R\u00e9pertoire de l'upload de la camera:",
"LabelCameraUploadPathHelp": "Si vous le souhaitez, vous pouvez choisir un r\u00e9pertoire d'upload personnalis\u00e9. Si vous ne mettez rien, le r\u00e9pertoire par d\u00e9faut sera utilis\u00e9. Si vous utilisez un r\u00e9pertoire personnalis\u00e9, vous devrez le rajouter \u00e0 la biblioth\u00e8que.",
"LabelCreateCameraUploadSubfolder": "Cr\u00e9er un sous-dossier pour chaque p\u00e9riph\u00e9rique",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer reel",
"OptionPlayUnwatchedTrailersOnly": "Lire seulement les bandes-annonces non lus.",
"HeaderTrailerReelHelp": "Commencer un \"trailer reel\" pour lire une longue liste de lecture de bandes-annonces.",
- "MessageNoTrailersFound": "Aucune bande-annonce trouv\u00e9e. Installer le plugin \"Trailer channel\" pour importer une biblioth\u00e8que de bandes-annonces Internet.",
+ "MessageNoTrailersFound": "Aucune bande-annonce trouv\u00e9e. Installez la cha\u00eene Bande-annonces pour am\u00e9liorer votre exp\u00e9rience, par l'ajout d'une biblioth\u00e8que de bandes-annonces disponibles sur Internet.",
"HeaderNewUsers": "Nouveaux utilisateurs",
"ButtonSignUp": "S'inscrire",
"ButtonForgotPassword": "Mot de passe oubli\u00e9 ?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limiter \u00e0 une seule image int\u00e9gr\u00e9e",
"LabelEnableSingleImageInDidlLimitHelp": "Quelques p\u00e9riph\u00e9riques ne fourniront pas un rendu correct si plusieurs images sont int\u00e9gr\u00e9es dans Didl",
"TabActivity": "Activit\u00e9",
- "TitleSync": "Sync."
+ "TitleSync": "Sync.",
+ "OptionAllowSyncContent": "Autoriser la synchronisation des media sur les p\u00e9riph\u00e9riques",
+ "NameSeasonUnknown": "Saison inconnue",
+ "NameSeasonNumber": "Saison {0}",
+ "LabelNewUserNameHelp": "Les noms d'utilisateur peuvent contenir des lettres (a-z), des chiffres (0-9), des tirets (-), des tirets bas (_), des apostrophes (') et des points (.).",
+ "TabJobs": "T\u00e2ches",
+ "TabSyncJobs": "T\u00e2ches de synchronisation"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/he.json b/MediaBrowser.Server.Implementations/Localization/Server/he.json
index cecde2022..0b5aef314 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/he.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/he.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "\u05d0\u05e9\u05e3 \u05d6\u05d4 \u05d9\u05e2\u05d6\u05d5\u05e8 \u05dc\u05da \u05d1\u05d4\u05ea\u05dc\u05d9\u05da \u05d4\u05d4\u05ea\u05e7\u05e0\u05d4.",
"TellUsAboutYourself": "\u05e1\u05e4\u05e8 \u05dc\u05e0\u05d5 \u05e2\u05dc \u05e2\u05e6\u05de\u05da",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "\u05e9\u05de\u05da \u05d4\u05e4\u05e8\u05d8\u05d9:",
"MoreUsersCanBeAddedLater": "\u05e0\u05d9\u05ea\u05df \u05dc\u05d4\u05d2\u05d3\u05d9\u05e8 \u05de\u05e9\u05ea\u05de\u05e9\u05d9\u05dd \u05e0\u05d5\u05e1\u05e4\u05d9\u05dd \u05de\u05d0\u05d5\u05d7\u05e8 \u05d9\u05d5\u05ea\u05e8 \u05d3\u05e8\u05da \u05dc\u05d5\u05d7 \u05d4\u05d1\u05e7\u05e8\u05d4.",
"UserProfilesIntro": "Media Browser \u05db\u05d5\u05dc\u05dc \u05ea\u05de\u05d9\u05db\u05d4 \u05de\u05d5\u05d1\u05e0\u05ea \u05d1\u05de\u05e1\u05e4\u05e8 \u05de\u05e9\u05ea\u05de\u05e9\u05d9\u05dd. \u05d5\u05de\u05d0\u05e4\u05e9\u05e8 \u05dc\u05db\u05dc \u05d0\u05d7\u05d3 \u05de\u05d4\u05dd \u05ea\u05e6\u05d5\u05d2\u05ea \u05d4\u05d2\u05d3\u05e8\u05d5\u05ea, \u05de\u05e6\u05d1 \u05e0\u05d2\u05df \u05d5\u05d1\u05e7\u05e8\u05ea \u05d4\u05d5\u05e8\u05d9\u05dd \u05d0\u05d9\u05e9\u05d9\u05ea.",
@@ -37,10 +38,22 @@
"ButtonOk": "\u05d0\u05e9\u05e8",
"ButtonCancel": "\u05d1\u05d8\u05dc",
"ButtonNew": "\u05d7\u05d3\u05e9",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Movies",
+ "FolderTypeMusic": "Music",
+ "FolderTypeAdultVideos": "Adult videos",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Music videos",
+ "FolderTypeHomeVideos": "Home videos",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Books",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "\u05d4\u05d2\u05d3\u05e8 \u05d0\u05ea \u05e1\u05e4\u05e8\u05d9\u05d9\u05ea \u05d4\u05de\u05d3\u05d9\u05d4 \u05e9\u05dc\u05da",
"ButtonAddMediaFolder": "\u05d4\u05d5\u05e1\u05e3 \u05ea\u05d9\u05e7\u05d9\u05d9\u05ea \u05de\u05d3\u05d9\u05d4",
"LabelFolderType": "\u05e1\u05d5\u05d2 \u05d4\u05ea\u05d9\u05e7\u05d9\u05d9\u05d4:",
- "MediaFolderHelpPluginRequired": "* \u05de\u05e6\u05e8\u05d9\u05da \u05de\u05d4\u05de\u05e9\u05ea\u05de\u05e9 \u05ea\u05d5\u05e1\u05e3, \u05dc\u05d3\u05d5\u05d2\u05de\u05d0 GameBrowser \u05d0\u05d5 MB Bookshelf",
"ReferToMediaLibraryWiki": "\u05e4\u05e0\u05d4 \u05dc\u05de\u05d9\u05d3\u05e2 \u05d0\u05d5\u05d3\u05d5\u05ea \u05e1\u05e4\u05e8\u05d9\u05d9\u05ea \u05d4\u05de\u05d3\u05d9\u05d4.",
"LabelCountry": "\u05de\u05d3\u05d9\u05e0\u05d4:",
"LabelLanguage": "\u05e9\u05e4\u05d4:",
@@ -52,12 +65,16 @@
"TabPreferences": "\u05d4\u05e2\u05d3\u05e4\u05d5\u05ea",
"TabPassword": "\u05e1\u05d9\u05e1\u05de\u05d0",
"TabLibraryAccess": "\u05d2\u05d9\u05e9\u05d4 \u05dc\u05ea\u05d9\u05e7\u05d9\u05d5\u05ea",
+ "TabAccess": "Access",
"TabImage": "\u05ea\u05de\u05d5\u05e0\u05d4",
"TabProfile": "\u05e4\u05e8\u05d5\u05e4\u05d9\u05dc",
"TabMetadata": "Metadata",
"TabImages": "\u05ea\u05de\u05d5\u05e0\u05d5\u05ea",
"TabNotifications": "\u05d4\u05ea\u05e8\u05d0\u05d5\u05ea",
"TabCollectionTitles": "\u05db\u05d5\u05ea\u05e8\u05d9\u05dd",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "\u05d4\u05e6\u05d2 \u05e4\u05e8\u05e7\u05d9\u05dd \u05d7\u05e1\u05e8\u05d9\u05dd \u05d1\u05ea\u05d5\u05da \u05d4\u05e2\u05d5\u05e0\u05d5\u05ea",
"LabelUnairedMissingEpisodesWithinSeasons": "\u05d4\u05e6\u05d2 \u05e4\u05e8\u05e7\u05d9\u05dd \u05e9\u05e2\u05d3\u05d9\u05df \u05d0\u05dc \u05e9\u05d5\u05d3\u05e8\u05d5 \u05d1\u05ea\u05d5\u05da \u05d4\u05e2\u05d5\u05e0\u05d5\u05ea",
"HeaderVideoPlaybackSettings": "\u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05e0\u05d9\u05d2\u05d5\u05df",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "New Users",
"ButtonSignUp": "Sign up",
"ButtonForgotPassword": "Forgot password?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/hr.json b/MediaBrowser.Server.Implementations/Localization/Server/hr.json
index f7589ff00..bfc21a6e2 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/hr.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/hr.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "Ovaj pomo\u0107nik \u0107e Vas voditi kroz proces pode\u0161avanja. Za po\u010detak, odaberite \u017eeljeni jezik.",
"TellUsAboutYourself": "Recite nam ne\u0161to o sebi",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "Ime:",
"MoreUsersCanBeAddedLater": "Vi\u0161e korisnika mo\u017eete dodati naknadno preko nadzorne plo\u010de.",
"UserProfilesIntro": "Media Browser ima ugra\u0111enu podr\u0161ku za korisni\u010dke profile, omogu\u0107uju\u0107i svakom korisniku da imaju svoje vlastite postavke prikaza i roditeljsku kontrolu.",
@@ -37,10 +38,22 @@
"ButtonOk": "Ok",
"ButtonCancel": "Odustani",
"ButtonNew": "Novo",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Movies",
+ "FolderTypeMusic": "Music",
+ "FolderTypeAdultVideos": "Adult videos",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Music videos",
+ "FolderTypeHomeVideos": "Home videos",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Books",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "Postavi svoju medijsku biblioteku",
"ButtonAddMediaFolder": "Dodaj mapu sa medijem",
"LabelFolderType": "Tip mape:",
- "MediaFolderHelpPluginRequired": "* Zahtjeva kori\u0161tenje dodatka, npr. GameBrowser ili MB Bookshelf.",
"ReferToMediaLibraryWiki": "Informirajte se o medijskoj bibilioteci wiki",
"LabelCountry": "Zemlja:",
"LabelLanguage": "Jezik:",
@@ -52,12 +65,16 @@
"TabPreferences": "Postavke",
"TabPassword": "Lozinka",
"TabLibraryAccess": "Pristup biblioteci",
+ "TabAccess": "Access",
"TabImage": "Slika",
"TabProfile": "Profil",
"TabMetadata": "Metadata",
"TabImages": "Slike",
"TabNotifications": "Obavijesti",
"TabCollectionTitles": "Naslovi",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "Prika\u017ei epizode koje nedostaju unutar sezone",
"LabelUnairedMissingEpisodesWithinSeasons": "Prika\u017ei epizode koje nisu emitirane unutar sezone",
"HeaderVideoPlaybackSettings": "Postavke video reprodukcije",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "New Users",
"ButtonSignUp": "Sign up",
"ButtonForgotPassword": "Forgot password?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/it.json b/MediaBrowser.Server.Implementations/Localization/Server/it.json
index 5daab33d8..8d4016abb 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/it.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/it.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media browser",
"ThisWizardWillGuideYou": "Procedura Guidata per l'installazione.",
"TellUsAboutYourself": "Parlaci di te",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "Nome",
"MoreUsersCanBeAddedLater": "Puoi aggiungere altri utenti in un secondo momento all'interno del pannello di configurazione",
"UserProfilesIntro": "Media Browser include il supporto integrato per i profili utente, permettendo ad ogni utente di avere le proprie impostazioni di visualizzazione.",
@@ -37,10 +38,22 @@
"ButtonOk": "OK",
"ButtonCancel": "Annulla",
"ButtonNew": "Nuovo",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "contenuto misto",
+ "FolderTypeMovies": "Film",
+ "FolderTypeMusic": "Musica",
+ "FolderTypeAdultVideos": "Video per adulti",
+ "FolderTypePhotos": "Foto",
+ "FolderTypeMusicVideos": "Video musicali",
+ "FolderTypeHomeVideos": "Video personali",
+ "FolderTypeGames": "Giochi",
+ "FolderTypeBooks": "Libri",
+ "FolderTypeTvShows": "Tv",
+ "FolderTypeInherit": "ereditare",
+ "LabelContentType": "Tipo di contenuto:",
"HeaderSetupLibrary": "Configura la tua libreria",
"ButtonAddMediaFolder": "Aggiungi cartella",
"LabelFolderType": "Tipo cartella",
- "MediaFolderHelpPluginRequired": "* Richiede l'uso di un plugin, ad esempio GameBrowser o MB Bookshelf.",
"ReferToMediaLibraryWiki": "Fare riferimento alla wiki libreria multimediale.",
"LabelCountry": "Nazione:",
"LabelLanguage": "lingua:",
@@ -52,12 +65,16 @@
"TabPreferences": "Preferenze",
"TabPassword": "Password",
"TabLibraryAccess": "Accesso libreria",
+ "TabAccess": "Accesso",
"TabImage": "Immagine",
"TabProfile": "Profilo",
"TabMetadata": "Metadata",
"TabImages": "Immagini",
"TabNotifications": "Notifiche",
"TabCollectionTitles": "Titolo",
+ "HeaderDeviceAccess": "Accesso dispositivo",
+ "OptionEnableAccessFromAllDevices": "Abilitare l'accesso da tutti i dispositivi",
+ "DeviceAccessHelp": "Questo vale solo per i dispositivi che possono essere identificati in modo univoco e non impedire l'accesso del browser. Filtraggio di accesso al dispositivo dell'utente impedir\u00e0 loro di usare nuovi dispositivi fino a quando non sono state approvate qui.",
"LabelDisplayMissingEpisodesWithinSeasons": "Visualizza gli episodi mancanti nelle stagioni",
"LabelUnairedMissingEpisodesWithinSeasons": "Visualizzare episodi mai andati in onda all'interno stagioni",
"HeaderVideoPlaybackSettings": "Impostazioni di riproduzione video",
@@ -232,10 +249,10 @@
"HeaderFeatureAccess": "Caratteristiche di accesso",
"OptionAllowMediaPlayback": "Consenti la riproduzione",
"OptionAllowBrowsingLiveTv": "Consenti la navigazione sulla Tv indiretta",
- "OptionAllowDeleteLibraryContent": "Allow deletion of library content",
+ "OptionAllowDeleteLibraryContent": "Consenti cancellazione di contenuti biblioteca",
"OptionAllowManageLiveTv": "Consenti la modifica delle operazioni pianificate della TV",
- "OptionAllowRemoteControlOthers": "Allow remote control of other users",
- "OptionAllowRemoteSharedDevices": "Allow remote control of shared devices",
+ "OptionAllowRemoteControlOthers": "Consenti controllo remoto di altri utenti",
+ "OptionAllowRemoteSharedDevices": "Consenti controllo remoto di dispositivi condivisi",
"OptionAllowRemoteSharedDevicesHelp": "Dispositivi DLNA sono considerati condivisa fino a quando un utente inizia controllarlo.",
"HeaderRemoteControl": "telecomando",
"OptionMissingTmdbId": "Tmdb Id mancante",
@@ -365,8 +382,8 @@
"LabelMaxScreenshotsPerItem": "Massimo numero di foto per oggetto:",
"LabelMinBackdropDownloadWidth": "Massima larghezza sfondo:",
"LabelMinScreenshotDownloadWidth": "Minima larghezza foto:",
- "ButtonAddScheduledTaskTrigger": "Aggiungi operazione:",
- "HeaderAddScheduledTaskTrigger": "Aggiungi operazione:",
+ "ButtonAddScheduledTaskTrigger": "Aggiungi operazione",
+ "HeaderAddScheduledTaskTrigger": "Aggiungi operazione",
"ButtonAdd": "Aggiungi",
"LabelTriggerType": "Tipo Evento:",
"OptionDaily": "Giornal.",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer b.",
"OptionPlayUnwatchedTrailersOnly": "Riproduci solo i trailer non visti",
"HeaderTrailerReelHelp": "Inizia a riprodurre una lunga playlist di trailer",
- "MessageNoTrailersFound": "Nessun Trailer trovato.Installa Il plug in dei traler per importare la libreria dei trailer da internet",
+ "MessageNoTrailersFound": "Nessun Trailer trovato.Installa Il plug in dei trailer per importare la libreria dei trailer da internet",
"HeaderNewUsers": "Nuovo Utente",
"ButtonSignUp": "Iscriviti",
"ButtonForgotPassword": "Dimenticato la password?",
@@ -1273,11 +1290,17 @@
"HeaderParentalRatings": "Valutazioni genitori",
"HeaderVideoTypes": "Tipi Video",
"HeaderYears": "Anni",
- "HeaderAddTag": "Add Tag",
- "LabelBlockItemsWithTags": "Block items with tags:",
+ "HeaderAddTag": "Aggiungi Tag",
+ "LabelBlockItemsWithTags": "Oggetti di blocco con tag:",
"LabelTag": "Tag:",
- "LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
- "LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
- "TabActivity": "Activity",
- "TitleSync": "Sync"
+ "LabelEnableSingleImageInDidlLimit": "Limitato a singola immagine incorporata",
+ "LabelEnableSingleImageInDidlLimitHelp": "Alcuni dispositivi non renderanno correttamente se pi\u00f9 immagini sono incorporati all'interno didl.",
+ "TabActivity": "Attivit\u00e0",
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Consenti sincronizzazione media per dispositivi",
+ "NameSeasonUnknown": "Stagione sconosciuto",
+ "NameSeasonNumber": "Stagione {0}",
+ "LabelNewUserNameHelp": "I nomi utente possono contenere lettere (az), numeri (0-9), trattini (-), underscore (_), apostrofi ('), e periodi (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/kk.json b/MediaBrowser.Server.Implementations/Localization/Server/kk.json
index 2d9e4053d..f78c49938 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/kk.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/kk.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "\u0411\u04b1\u043b \u043a\u043e\u043c\u0435\u043a\u0448\u0456 \u043e\u0440\u043d\u0430\u0442\u0443 \u0436\u04d9\u043d\u0435 \u0442\u0435\u04a3\u0448\u0435\u0443 \u043f\u0440\u043e\u0446\u0435\u0441\u0456 \u0441\u0430\u0442\u044b\u043b\u0430\u0440\u044b\u043c\u0435\u043d \u04e9\u0442\u043a\u0456\u0437\u0435\u0434\u0456. \u0411\u0430\u0441\u0442\u0430\u0443 \u04af\u0448\u0456\u043d \u04e9\u0437\u0456\u04a3\u0456\u0437\u0433\u0435 \u0442\u0456\u043b \u0442\u0435\u04a3\u0448\u0435\u043b\u0456\u043c\u0456\u043d \u0442\u0430\u04a3\u0434\u0430\u04a3\u044b\u0437.",
"TellUsAboutYourself": "\u04e8\u0437\u0456\u04a3\u0456\u0437 \u0442\u0443\u0440\u0430\u043b\u044b \u0430\u0439\u0442\u044b\u04a3\u044b\u0437",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "\u0410\u0442\u044b\u04a3\u044b\u0437:",
"MoreUsersCanBeAddedLater": "\u041a\u04e9\u0431\u0456\u0440\u0435\u043a \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b\u043b\u0430\u0440\u0434\u044b \u043a\u0435\u0439\u0456\u043d \u0411\u0430\u049b\u044b\u043b\u0430\u0443 \u0442\u0430\u049b\u0442\u0430\u0441\u044b \u0430\u0440\u049b\u044b\u043b\u044b \u04af\u0441\u0442\u0435\u0443\u0456\u04a3\u0456\u0437 \u043c\u04af\u043c\u043a\u0456\u043d.",
"UserProfilesIntro": "Media Browser \u0431\u0435\u043a\u0456\u0442\u0456\u043b\u0433\u0435\u043d \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b \u043f\u0440\u043e\u0444\u0438\u043b\u044c\u0434\u0435\u0440\u0456\u043d \u049b\u0430\u043c\u0442\u0438\u0434\u044b, \u04d9\u0440 \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b\u0493\u0430 \u04e9\u0437\u0456\u043d\u0456\u04a3 \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0443 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043b\u0435\u0440\u0456, \u043e\u0439\u043d\u0430\u0442\u0443 \u043a\u04af\u0439\u0456 \u0436\u04d9\u043d\u0435 \u0436\u0430\u0441\u0442\u0430\u0441 \u0441\u0430\u043d\u0430\u0442\u044b \u049b\u043e\u0441\u044b\u043b\u0430\u0434\u044b.",
@@ -37,10 +38,22 @@
"ButtonOk": "\u0416\u0430\u0440\u0430\u0439\u0434\u044b",
"ButtonCancel": "\u0411\u043e\u043b\u0434\u044b\u0440\u043c\u0430\u0443",
"ButtonNew": "\u0416\u0430\u0441\u0430\u0443",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "\u0410\u0440\u0430\u043b\u0430\u0441 \u043c\u0430\u0437\u043c\u04b1\u043d",
+ "FolderTypeMovies": "\u0424\u0438\u043b\u044c\u043c\u0434\u0435\u0440",
+ "FolderTypeMusic": "\u041c\u0443\u0437\u044b\u043a\u0430",
+ "FolderTypeAdultVideos": "\u0415\u0440\u0435\u0441\u0435\u043a\u0442\u0456\u043a \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0440",
+ "FolderTypePhotos": "\u0424\u043e\u0442\u043e\u0441\u0443\u0440\u0435\u0442\u0442\u0435\u0440",
+ "FolderTypeMusicVideos": "\u041c\u0443\u0437\u044b\u043a\u0430\u043b\u044b\u049b \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0440",
+ "FolderTypeHomeVideos": "\u04ae\u0439 \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0440\u0456",
+ "FolderTypeGames": "\u041e\u0439\u044b\u043d\u0434\u0430\u0440",
+ "FolderTypeBooks": "\u041a\u0456\u0442\u0430\u043f\u0442\u0430\u0440",
+ "FolderTypeTvShows": "\u0422\u0414",
+ "FolderTypeInherit": "\u041c\u04b1\u0440\u0430\u0493\u0430 \u0438\u0435\u043b\u0435\u043d\u0443",
+ "LabelContentType": "\u041c\u0430\u0437\u043c\u04b1\u043d \u0442\u04af\u0440\u0456:",
"HeaderSetupLibrary": "\u0422\u0430\u0441\u0443\u0448\u044b\u0445\u0430\u043d\u0430\u043d\u044b \u043e\u0440\u043d\u0430\u0442\u0443 \u0436\u04d9\u043d\u0435 \u0442\u0435\u04a3\u0448\u0435\u0443",
"ButtonAddMediaFolder": "\u0422\u0430\u0441\u0443\u0448\u044b \u049b\u0430\u043b\u0442\u0430\u0441\u044b\u043d \u04af\u0441\u0442\u0435\u0443",
"LabelFolderType": "\u049a\u0430\u043b\u0442\u0430 \u0442\u04af\u0440\u0456:",
- "MediaFolderHelpPluginRequired": "* \u041f\u043b\u0430\u0433\u0438\u043d\u0434\u0456 \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u044b\u04a3\u044b\u0437, \u043c\u044b\u0441\u0430\u043b\u044b, GameBrowser, \u043d\u0435 MB Bookshelf.",
"ReferToMediaLibraryWiki": "\u0422\u0430\u0441\u0443\u0448\u044b\u0445\u0430\u043d\u0430 \u0442\u0443\u0440\u0430\u043b\u044b \u0443\u0438\u043a\u0438 \u0456\u0448\u0456\u043d\u0435\u043d \u049b\u0430\u0440\u0430\u04a3\u044b\u0437.",
"LabelCountry": "\u0415\u043b:",
"LabelLanguage": "\u0422\u0456\u043b:",
@@ -52,12 +65,16 @@
"TabPreferences": "\u0422\u0435\u04a3\u0448\u0435\u043b\u0456\u043c\u0434\u0435\u0440",
"TabPassword": "\u049a\u04b1\u043f\u0438\u044f \u0441\u04e9\u0437",
"TabLibraryAccess": "\u0422\u0430\u0441\u0443\u0448\u044b\u0445\u0430\u043d\u0430\u0493\u0430 \u049b\u0430\u0442\u044b\u043d\u0430\u0441",
+ "TabAccess": "\u049a\u0430\u0442\u044b\u043d\u0430\u0441\u0443",
"TabImage": "\u0421\u0443\u0440\u0435\u0442",
"TabProfile": "\u041f\u0440\u043e\u0444\u0438\u043b\u044c",
"TabMetadata": "\u041c\u0435\u0442\u0430\u0434\u0435\u0440\u0435\u043a",
"TabImages": "\u0421\u0443\u0440\u0435\u0442\u0442\u0435\u0440",
"TabNotifications": "\u0425\u0430\u0431\u0430\u0440\u043b\u0430\u043d\u0434\u044b\u0440\u0443\u043b\u0430\u0440",
"TabCollectionTitles": "\u0422\u0443\u044b\u043d\u0434\u044b\u043b\u0430\u0440",
+ "HeaderDeviceAccess": "\u049a\u04b1\u0440\u044b\u043b\u0493\u044b\u0493\u0430 \u049b\u0430\u0442\u044b\u043d\u0430\u0441",
+ "OptionEnableAccessFromAllDevices": "\u0411\u0430\u0440\u043b\u044b\u049b \u049b\u04b1\u0440\u044b\u043b\u0493\u044b\u043b\u0430\u0440\u0434\u0430\u043d \u049b\u0430\u0442\u044b\u043d\u0430\u0441\u0443\u0434\u044b \u049b\u043e\u0441\u0443",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "\u0416\u043e\u049b \u044d\u043f\u0438\u0437\u043e\u0434\u0442\u0430\u0440\u0434\u044b \u043c\u0430\u0443\u0441\u044b\u043c \u0456\u0448\u0456\u043d\u0434\u0435 \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0443",
"LabelUnairedMissingEpisodesWithinSeasons": "\u041a\u04e9\u0440\u0441\u0435\u0442\u0456\u043b\u043c\u0435\u0433\u0435\u043d \u044d\u043f\u0438\u0437\u043e\u0434\u0442\u0430\u0440\u0434\u044b \u043c\u0430\u0443\u0441\u044b\u043c \u0456\u0448\u0456\u043d\u0434\u0435 \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0443",
"HeaderVideoPlaybackSettings": "\u0411\u0435\u0439\u043d\u0435 \u043e\u0439\u043d\u0430\u0442\u0443 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043b\u0435\u0440\u0456",
@@ -365,8 +382,8 @@
"LabelMaxScreenshotsPerItem": "\u042d\u043b\u0435\u043c\u0435\u043d\u0442 \u0431\u043e\u0439\u044b\u043d\u0448\u0430 \u0435\u04a3 \u043a\u04e9\u043f \u0441\u043a\u0440\u0438\u043d\u0448\u043e\u0442 \u0441\u0430\u043d\u044b:",
"LabelMinBackdropDownloadWidth": "\u0410\u0440\u0442\u049b\u044b \u0441\u0443\u0440\u0435\u0442\u0442\u0456\u04a3 \u0436\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u044b\u043d\u0430\u0442\u044b\u043d \u0435\u04a3 \u0430\u0437 \u0435\u043d\u0456:",
"LabelMinScreenshotDownloadWidth": "\u0416\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u0443 \u04af\u0448\u0456\u043d \u0435\u04a3 \u0430\u0437 \u0441\u043a\u0440\u0438\u043d\u0448\u043e\u0442 \u0435\u043d\u0456:",
- "ButtonAddScheduledTaskTrigger": "\u0422\u0430\u043f\u0441\u044b\u0440\u043c\u0430 \u0442\u0440\u0438\u0433\u0433\u0435\u0440\u0456\u043d \u04af\u0441\u0442\u0435\u0443",
- "HeaderAddScheduledTaskTrigger": "\u0422\u0430\u043f\u0441\u044b\u0440\u043c\u0430 \u0442\u0440\u0438\u0433\u0433\u0435\u0440\u0456\u043d \u04af\u0441\u0442\u0435\u0443",
+ "ButtonAddScheduledTaskTrigger": "\u0422\u0440\u0438\u0433\u0433\u0435\u0440\u0434\u0456 \u04af\u0441\u0442\u0435\u0443",
+ "HeaderAddScheduledTaskTrigger": "\u0422\u0440\u0438\u0433\u0433\u0435\u0440\u0434\u0456 \u04af\u0441\u0442\u0435\u0443",
"ButtonAdd": "\u04ae\u0441\u0442\u0435\u0443",
"LabelTriggerType": "\u0422\u0440\u0438\u0433\u0433\u0435\u0440 \u0442\u04af\u0440\u0456:",
"OptionDaily": "\u041a\u04af\u043d \u0441\u0430\u0439\u044b\u043d",
@@ -683,9 +700,9 @@
"OptionProfilePhoto": "\u0424\u043e\u0442\u043e",
"LabelUserLibrary": "\u041f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b \u0442\u0430\u0441\u0443\u0448\u044b\u0445\u0430\u043d\u0430\u0441\u044b",
"LabelUserLibraryHelp": "\u049a\u04b1\u0440\u044b\u043b\u0493\u044b\u0434\u0430 \u049b\u0430\u0439 \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b \u0442\u0430\u0441\u0443\u0448\u044b\u0445\u0430\u043d\u0430\u0441\u044b\u043d \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0443\u0456\u043d \u0442\u0430\u04a3\u0434\u0430\u04a3\u044b\u0437. \u04d8\u0434\u0435\u043f\u043a\u0456 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u043c\u04b1\u0440\u0430\u0441\u044b\u043d\u0430 \u0438\u0435\u043b\u0435\u043d\u0443 \u04af\u0448\u0456\u043d \u0431\u043e\u0441 \u049b\u0430\u043b\u0434\u044b\u0440\u044b\u04a3\u044b\u0437.",
- "OptionPlainStorageFolders": "\u0411\u0430\u0440\u043b\u044b\u049b \u049b\u0430\u043b\u0442\u0430\u043b\u0430\u0440\u0434\u044b \u0436\u0430\u0439 \u0441\u0430\u049b\u0442\u0430\u0443 \u049b\u0430\u043b\u0442\u0430\u043b\u0430\u0440\u044b \u0440\u0435\u0442\u0456\u043d\u0434\u0435 \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0443",
+ "OptionPlainStorageFolders": "\u0411\u0430\u0440\u043b\u044b\u049b \u049b\u0430\u043b\u0442\u0430\u043b\u0430\u0440\u0434\u044b \u043a\u04d9\u0434\u0456\u043c\u0433\u0456 \u0441\u0430\u049b\u0442\u0430\u0443 \u049b\u0430\u043b\u0442\u0430\u043b\u0430\u0440\u044b \u0440\u0435\u0442\u0456\u043d\u0434\u0435 \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0443",
"OptionPlainStorageFoldersHelp": "\u049a\u043e\u0441\u044b\u043b\u0493\u0430\u043d\u0434\u0430, \u0431\u0430\u0440\u043b\u044b\u049b \u049b\u0430\u043b\u0442\u0430\u043b\u0430\u0440 DIDL \u0456\u0448\u0456\u043d\u0434\u0435 \"object.container.person.musicArtist\" \u0441\u0438\u044f\u049b\u0442\u044b \u043d\u0430\u049b\u0442\u044b\u043b\u0430\u0443 \u0442\u04af\u0440\u0456\u043d\u0456\u04a3 \u043e\u0440\u043d\u044b\u043d\u0430 \"object.container.storageFolder\" \u0431\u043e\u043b\u044b\u043f \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u043d\u0435\u0434\u0456.",
- "OptionPlainVideoItems": "\u0411\u0430\u0440\u043b\u044b\u049b \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0440\u0434\u0456 \u0436\u0430\u0439 \u0431\u0435\u0439\u043d\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0442\u0435\u0440\u0456 \u0440\u0435\u0442\u0456\u043d\u0434\u0435 \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0443",
+ "OptionPlainVideoItems": "\u0411\u0430\u0440\u043b\u044b\u049b \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0440\u0434\u0456 \u043a\u04d9\u0434\u0456\u043c\u0433\u0456 \u0431\u0435\u0439\u043d\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0442\u0435\u0440\u0456 \u0440\u0435\u0442\u0456\u043d\u0434\u0435 \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0443",
"OptionPlainVideoItemsHelp": "\u049a\u043e\u0441\u044b\u043b\u0493\u0430\u043d\u0434\u0430, \u0431\u0430\u0440\u043b\u044b\u049b \u049b\u0430\u043b\u0442\u0430\u043b\u0430\u0440 DIDL \u0456\u0448\u0456\u043d\u0434\u0435 \"object.item.videoItem.movie\" \u0441\u0438\u044f\u049b\u0442\u044b \u043d\u0430\u049b\u0442\u044b\u043b\u0430\u0443 \u0442\u04af\u0440\u0456\u043d\u0456\u04a3 \u043e\u0440\u043d\u044b\u043d\u0430 \"object.item.videoItem\" \u0431\u043e\u043b\u044b\u043f \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u043d\u0435\u0434\u0456.",
"LabelSupportedMediaTypes": "\u049a\u043e\u043b\u0434\u0430\u0443\u0434\u0430\u0493\u044b \u0442\u0430\u0441\u0443\u0448\u044b\u0434\u0435\u0440\u0435\u043a\u0442\u0435\u0440 \u0442\u04af\u0440\u043b\u0435\u0440\u0456:",
"TabIdentification": "\u0410\u043d\u044b\u049b\u0442\u0430\u0443",
@@ -1050,7 +1067,7 @@
"OptionOthers": "\u0411\u0430\u0441\u049b\u0430\u043b\u0430\u0440",
"HeaderDownloadPeopleMetadataForHelp": "\u049a\u043e\u0441\u044b\u043c\u0448\u0430 \u0442\u0435\u04a3\u0448\u0435\u043b\u0456\u043c\u0434\u0435\u0440\u0434\u0456 \u049b\u043e\u0441\u049b\u0430\u043d\u0434\u0430 \u044d\u043a\u0440\u0430\u043d\u0434\u0430\u0493\u044b \u0430\u049b\u043f\u0430\u0440\u0430\u0442\u0442\u044b \u043a\u04e9\u0431\u0456\u0440\u0435\u043a \u04b1\u0441\u044b\u043d\u0430\u0434\u044b, \u0431\u0456\u0440\u0430\u049b \u0442\u0430\u0441\u0443\u0448\u044b\u0445\u0430\u043d\u0430\u043d\u044b\u04a3 \u0441\u043a\u0430\u043d\u0435\u0440\u043b\u0435\u0443\u043b\u0435\u0440\u0456 \u0431\u0430\u044f\u0443\u043b\u0430\u0439\u0434\u044b.",
"ViewTypeFolders": "\u049a\u0430\u043b\u0442\u0430\u043b\u0430\u0440",
- "LabelDisplayFoldersView": "\u0416\u0430\u0439 \u0442\u0430\u0441\u0443\u0448\u044b \u049b\u0430\u043b\u0442\u0430\u043b\u0430\u0440\u044b\u043d \u043a\u04e9\u0440\u0441\u0435\u0442\u0443 \u04af\u0448\u0456\u043d \u049a\u0430\u043b\u0442\u0430\u043b\u0430\u0440 \u0430\u0441\u043f\u0435\u043a\u0442\u0456\u043d \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0443",
+ "LabelDisplayFoldersView": "\u041a\u04d9\u0434\u0456\u043c\u0433\u0456 \u0442\u0430\u0441\u0443\u0448\u044b \u049b\u0430\u043b\u0442\u0430\u043b\u0430\u0440\u044b\u043d \u043a\u04e9\u0440\u0441\u0435\u0442\u0443 \u04af\u0448\u0456\u043d \u049a\u0430\u043b\u0442\u0430\u043b\u0430\u0440 \u0430\u0441\u043f\u0435\u043a\u0442\u0456\u043d \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u0443",
"ViewTypeLiveTvRecordingGroups": "\u0416\u0430\u0437\u0431\u0430\u043b\u0430\u0440",
"ViewTypeLiveTvChannels": "\u0410\u0440\u043d\u0430\u043b\u0430\u0440",
"LabelAllowLocalAccessWithoutPassword": "\u049a\u04b1\u043f\u0438\u044f \u0441\u04e9\u0437\u0441\u0456\u0437 \u0436\u0435\u0440\u0433\u0456\u043b\u0456\u043a\u0442\u0456 \u049b\u0430\u0442\u044b\u043d\u0441\u0443 \u04af\u0448\u0456\u043d \u0440\u04b1\u049b\u0441\u0430\u0442 \u0435\u0442\u0443",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "\u0422\u0440\u0435\u0439\u043b\u0435\u0440\u043b\u0435\u0440\u0434\u0456 \u0436\u0430\u043f\u0441\u044b\u0440\u0443",
"OptionPlayUnwatchedTrailersOnly": "\u0422\u0435\u043a \u049b\u0430\u043d\u0430 \u049b\u0430\u0440\u0430\u043b\u043c\u0430\u0493\u0430\u043d \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043b\u0435\u0440\u0434\u0456 \u043e\u0439\u043d\u0430\u0442\u0443",
"HeaderTrailerReelHelp": "\u04b0\u0437\u0430\u049b \u043e\u0440\u044b\u043d\u0434\u0430\u043b\u0430\u0442\u044b\u043d \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043b\u0435\u0440 \u043e\u0439\u043d\u0430\u0442\u0443 \u0442\u0456\u0437\u0456\u043c\u0456\u043d \u043e\u0439\u043d\u0430\u0442\u0443 \u04af\u0448\u0456\u043d \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043b\u0435\u0440\u0434\u0456 \u0436\u0430\u043f\u0441\u044b\u0440\u0443\u0434\u044b \u0431\u0430\u0441\u0442\u0430\u04a3\u044b\u0437.",
- "MessageNoTrailersFound": "\u0415\u0448\u049b\u0430\u043d\u0434\u0430\u0439 \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043b\u0435\u0440 \u0442\u0430\u0431\u044b\u043b\u043c\u0430\u0434\u044b. \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442-\u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043b\u0435\u0440 \u049b\u043e\u0440\u044b\u043d \u0438\u043c\u043f\u043e\u0440\u0442\u0442\u0430\u0443 \u04af\u0448\u0456\u043d \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043b\u0435\u0440 \u0430\u0440\u043d\u0430\u0441\u044b \u043f\u043b\u0430\u0433\u0438\u043d\u0456\u043d \u043e\u0440\u043d\u0430\u0442\u044b\u04a3\u044b\u0437.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "\u0416\u0430\u04a3\u0430 \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b\u043b\u0430\u0440",
"ButtonSignUp": "\u0422\u0456\u0440\u043a\u0435\u043b\u0443",
"ButtonForgotPassword": "\u049a\u04b1\u043f\u0438\u044f \u0441\u04e9\u0437\u0434\u0456 \u04b1\u043c\u044b\u0442\u044b\u04a3\u044b\u0437 \u0431\u0430?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "\u0416\u0430\u043b\u0493\u044b\u0437 \u043a\u0456\u0440\u0456\u0441\u0442\u0456\u0440\u0456\u043b\u0433\u0435\u043d \u0441\u0443\u0440\u0435\u0442\u043a\u0435 \u0448\u0435\u043a\u0442\u0435\u0443",
"LabelEnableSingleImageInDidlLimitHelp": "\u0415\u0433\u0435\u0440 \u0431\u0456\u0440\u043d\u0435\u0448\u0435 \u0441\u0443\u0440\u0435\u0442 Didl \u0456\u0448\u0456\u043d\u0435 \u043a\u0456\u0440\u0456\u0441\u0442\u0456\u0440\u0456\u043b\u0441\u0435, \u043a\u0435\u0439\u0431\u0456\u0440 \u049b\u04b1\u0440\u044b\u043b\u0493\u044b\u043b\u0430\u0440\u0434\u0430 \u0442\u0438\u0456\u0441\u0442\u0456 \u0442\u04af\u0440\u0434\u0435 \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u043d\u0431\u0435\u0439\u0434\u0456.",
"TabActivity": "\u04d8\u0440\u0435\u043a\u0435\u0442\u0442\u0435\u0440",
- "TitleSync": "\u0421\u0438\u043d\u0445\u0440\u043e\u043d\u0434\u0430\u0443"
+ "TitleSync": "\u0421\u0438\u043d\u0445\u0440\u043e\u043d\u0434\u0430\u0443",
+ "OptionAllowSyncContent": "\u041c\u0435\u0434\u0438\u0430\u0434\u0435\u0440\u0435\u043a\u0442\u0435\u0440\u0434\u0456 \u049b\u04b1\u0440\u044b\u043b\u0493\u044b\u043c\u0435\u043d \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0434\u0430\u0443\u0493\u0430 \u0440\u04b1\u049b\u0441\u0430\u0442 \u0435\u0442\u0443",
+ "NameSeasonUnknown": "\u0411\u0435\u043b\u0433\u0456\u0441\u0456\u0437 \u043c\u0430\u0443\u0441\u044b\u043c",
+ "NameSeasonNumber": "{0}-\u0441\u0435\u0437\u043e\u043d",
+ "LabelNewUserNameHelp": "\u041f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b \u0430\u0442\u0442\u0430\u0440\u044b\u043d\u0434\u0430 \u04d9\u0440\u0456\u043f\u0442\u0435\u0440 (a-z), \u0441\u0430\u043d\u0434\u0430\u0440 (0-9), \u0441\u044b\u0437\u044b\u049b\u0448\u0430\u043b\u0430\u0440 (-), \u0430\u0441\u0442\u044b\u04a3\u0493\u044b \u0441\u044b\u0437\u044b\u049b\u0442\u0430\u0440 (_), \u0434\u04d9\u0439\u0435\u043a\u0448\u0435\u043b\u0435\u0440 (') \u0436\u04d9\u043d\u0435 \u043d\u04af\u043a\u0442\u0435\u043b\u0435\u0440 (.) \u0431\u043e\u043b\u0443\u044b \u043c\u04af\u043c\u043a\u0456\u043d",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ko.json b/MediaBrowser.Server.Implementations/Localization/Server/ko.json
index 2a0bcc74f..fd5e0d401 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/ko.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/ko.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "This wizard will help guide you through the setup process. To begin, please select your preferred language.",
"TellUsAboutYourself": "Tell us about yourself",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "Your first name:",
"MoreUsersCanBeAddedLater": "More users can be added later within the Dashboard.",
"UserProfilesIntro": "Media Browser includes built-in support for user profiles, enabling each user to have their own display settings, playstate and parental controls.",
@@ -37,10 +38,22 @@
"ButtonOk": "Ok",
"ButtonCancel": "Cancel",
"ButtonNew": "New",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Movies",
+ "FolderTypeMusic": "Music",
+ "FolderTypeAdultVideos": "Adult videos",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Music videos",
+ "FolderTypeHomeVideos": "Home videos",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Books",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "Setup your media library",
"ButtonAddMediaFolder": "Add media folder",
"LabelFolderType": "Folder type:",
- "MediaFolderHelpPluginRequired": "* Requires the use of a plugin, e.g. GameBrowser or MB Bookshelf.",
"ReferToMediaLibraryWiki": "Refer to the media library wiki.",
"LabelCountry": "Country:",
"LabelLanguage": "Language:",
@@ -52,12 +65,16 @@
"TabPreferences": "Preferences",
"TabPassword": "Password",
"TabLibraryAccess": "Library Access",
+ "TabAccess": "Access",
"TabImage": "Image",
"TabProfile": "Profile",
"TabMetadata": "Metadata",
"TabImages": "Images",
"TabNotifications": "Notifications",
"TabCollectionTitles": "Titles",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "Display missing episodes within seasons",
"LabelUnairedMissingEpisodesWithinSeasons": "Display unaired episodes within seasons",
"HeaderVideoPlaybackSettings": "Video Playback Settings",
@@ -365,8 +382,8 @@
"LabelMaxScreenshotsPerItem": "Maximum number of screenshots per item:",
"LabelMinBackdropDownloadWidth": "Minimum backdrop download width:",
"LabelMinScreenshotDownloadWidth": "Minimum screenshot download width:",
- "ButtonAddScheduledTaskTrigger": "Add Task Trigger",
- "HeaderAddScheduledTaskTrigger": "Add Task Trigger",
+ "ButtonAddScheduledTaskTrigger": "Add Trigger",
+ "HeaderAddScheduledTaskTrigger": "Add Trigger",
"ButtonAdd": "Add",
"LabelTriggerType": "Trigger Type:",
"OptionDaily": "Daily",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "New Users",
"ButtonSignUp": "Sign up",
"ButtonForgotPassword": "Forgot password?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ms.json b/MediaBrowser.Server.Implementations/Localization/Server/ms.json
index adbe36a92..e9308bd7a 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/ms.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/ms.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "This wizard will help guide you through the setup process. To begin, please select your preferred language.",
"TellUsAboutYourself": "Tell us about yourself",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "Your first name:",
"MoreUsersCanBeAddedLater": "More users can be added later within the Dashboard.",
"UserProfilesIntro": "Media Browser includes built-in support for user profiles, enabling each user to have their own display settings, playstate and parental controls.",
@@ -37,10 +38,22 @@
"ButtonOk": "Ok",
"ButtonCancel": "Cancel",
"ButtonNew": "New",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Movies",
+ "FolderTypeMusic": "Music",
+ "FolderTypeAdultVideos": "Adult videos",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Music videos",
+ "FolderTypeHomeVideos": "Home videos",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Books",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "Setup your media library",
"ButtonAddMediaFolder": "Add media folder",
"LabelFolderType": "Folder type:",
- "MediaFolderHelpPluginRequired": "* Requires the use of a plugin, e.g. GameBrowser or MB Bookshelf.",
"ReferToMediaLibraryWiki": "Refer to the media library wiki.",
"LabelCountry": "Country:",
"LabelLanguage": "Language:",
@@ -52,12 +65,16 @@
"TabPreferences": "Preferences",
"TabPassword": "Password",
"TabLibraryAccess": "Library Access",
+ "TabAccess": "Access",
"TabImage": "Image",
"TabProfile": "Profile",
"TabMetadata": "Metadata",
"TabImages": "Images",
"TabNotifications": "Notifications",
"TabCollectionTitles": "Titles",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "Display missing episodes within seasons",
"LabelUnairedMissingEpisodesWithinSeasons": "Display unaired episodes within seasons",
"HeaderVideoPlaybackSettings": "Video Playback Settings",
@@ -365,8 +382,8 @@
"LabelMaxScreenshotsPerItem": "Maximum number of screenshots per item:",
"LabelMinBackdropDownloadWidth": "Minimum backdrop download width:",
"LabelMinScreenshotDownloadWidth": "Minimum screenshot download width:",
- "ButtonAddScheduledTaskTrigger": "Add Task Trigger",
- "HeaderAddScheduledTaskTrigger": "Add Task Trigger",
+ "ButtonAddScheduledTaskTrigger": "Add Trigger",
+ "HeaderAddScheduledTaskTrigger": "Add Trigger",
"ButtonAdd": "Add",
"LabelTriggerType": "Trigger Type:",
"OptionDaily": "Daily",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "New Users",
"ButtonSignUp": "Sign up",
"ButtonForgotPassword": "Forgot password?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/nb.json b/MediaBrowser.Server.Implementations/Localization/Server/nb.json
index a6a1fa3c4..651f3522d 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/nb.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/nb.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "Denne wizarden vil guide deg gjennom server-konfigurasjonen. For \u00e5 begynne, vennligst velg ditt foretrukne spr\u00e5k.",
"TellUsAboutYourself": "Fortell om deg selv",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "Ditt fornavn",
"MoreUsersCanBeAddedLater": "Du kan legge til flere brukere senere via Dashboard",
"UserProfilesIntro": "Media Browser inkluderer innebygd st\u00f8tte for bruker profiler som tilbyr brukere innstillinger for visning, avspillerstatus og begrensning p\u00e5 innhold.",
@@ -37,10 +38,22 @@
"ButtonOk": "Ok",
"ButtonCancel": "avbryt",
"ButtonNew": "Ny",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Filmer",
+ "FolderTypeMusic": "Musikk",
+ "FolderTypeAdultVideos": "Voksen videoer",
+ "FolderTypePhotos": "Foto",
+ "FolderTypeMusicVideos": "Musikk videoer",
+ "FolderTypeHomeVideos": "Hjemme videoer",
+ "FolderTypeGames": "Spill",
+ "FolderTypeBooks": "B\u00f8ker",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "Konfigurer media-biblioteket",
"ButtonAddMediaFolder": "Legg til media-mappe",
"LabelFolderType": "Mappe typpe",
- "MediaFolderHelpPluginRequired": "* Krever bruk av et programtillegg f.eks GameBrowser eller MB Bookshelf.",
"ReferToMediaLibraryWiki": "Se i media-bibliotek wikien",
"LabelCountry": "LAnd",
"LabelLanguage": "Spr\u00e5k:",
@@ -52,12 +65,16 @@
"TabPreferences": "Preferanser",
"TabPassword": "Passord",
"TabLibraryAccess": "Bibliotektilgang",
+ "TabAccess": "Access",
"TabImage": "Bilde",
"TabProfile": "profil",
"TabMetadata": "Metadata",
"TabImages": "Bilder",
"TabNotifications": "Varslinger",
"TabCollectionTitles": "Titler",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "Vis episoder som sesongen mangler",
"LabelUnairedMissingEpisodesWithinSeasons": "Vis episoder som enn\u00e5 ikke har blitt sendt",
"HeaderVideoPlaybackSettings": "Innstillinger for video-avspilling",
@@ -232,9 +249,9 @@
"HeaderFeatureAccess": "Funksjon Tilgang",
"OptionAllowMediaPlayback": "Tillatt medieavspilling",
"OptionAllowBrowsingLiveTv": "Tillat surfing av Live TV",
- "OptionAllowDeleteLibraryContent": "Allow deletion of library content",
+ "OptionAllowDeleteLibraryContent": "Tillat sletting av bibliotek innhold",
"OptionAllowManageLiveTv": "Tillat styring av Live TV opptak",
- "OptionAllowRemoteControlOthers": "Allow remote control of other users",
+ "OptionAllowRemoteControlOthers": "Tillat fjernstyring av andre brukere",
"OptionAllowRemoteSharedDevices": "Allow remote control of shared devices",
"OptionAllowRemoteSharedDevicesHelp": "Dlna devices are considered shared until a user begins controlling it.",
"HeaderRemoteControl": "Remote Control",
@@ -459,7 +476,7 @@
"LinkApiDocumentation": "Api Dokumentasjon",
"LabelFriendlyServerName": "Vennlig server navn:",
"LabelFriendlyServerNameHelp": "Dette navnet vil bli brukt for \u00e5 identifisere denne serveren. Hvis feltet er tomt, vil maskinens navn bli brukt.",
- "LabelPreferredDisplayLanguage": "Preferred display language:",
+ "LabelPreferredDisplayLanguage": "Foretrukket skjerm spr\u00e5k:",
"LabelPreferredDisplayLanguageHelp": "Oversetting av Media Browser er ett p\u00e5g\u00e5ende prosjekt og er enda ikke fullstendig ferdig.",
"LabelReadHowYouCanContribute": "Les mer om hvordan du kan bidra.",
"HeaderNewCollection": "Ny Samling",
@@ -1228,7 +1245,7 @@
"HeaderSignInWithConnect": "Sign in with Media Browser Connect",
"HeaderGuests": "Gjester",
"HeaderLocalUsers": "Lokale Brukere",
- "HeaderPendingInvitations": "Pending Invitations",
+ "HeaderPendingInvitations": "Ventende invitasjoner",
"TabParentalControl": "Foreldrekontroll",
"HeaderAccessSchedule": "Access Schedule",
"HeaderAccessScheduleHelp": "Create an access schedule to limit access to certain hours.",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "Nye Brukere",
"ButtonSignUp": "Registrering",
"ButtonForgotPassword": "Glemt passord?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/nl.json b/MediaBrowser.Server.Implementations/Localization/Server/nl.json
index c4f65de2c..3a0dd079d 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/nl.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/nl.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "Deze wizard helpt u door het setup-proces.",
"TellUsAboutYourself": "Vertel ons over u zelf",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "Uw voornaam:",
"MoreUsersCanBeAddedLater": "Meer gebruikers kunnen later via het dashboard worden toegevoegd.",
"UserProfilesIntro": "Media Browser bevat ingebouwde ondersteuning voor gebruikersprofielen, zodat iedere gebruiker zijn eigen display-instellingen, afspeelstatus en ouderlijk toezicht heeft.",
@@ -37,10 +38,22 @@
"ButtonOk": "Ok",
"ButtonCancel": "Annuleren",
"ButtonNew": "Nieuw",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Gemengde inhoud",
+ "FolderTypeMovies": "Films",
+ "FolderTypeMusic": "Muziek",
+ "FolderTypeAdultVideos": "Adult video's",
+ "FolderTypePhotos": "Foto's",
+ "FolderTypeMusicVideos": "Muziek video's",
+ "FolderTypeHomeVideos": "Thuis video's",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Boeken",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "overerven",
+ "LabelContentType": "Inhoud type:",
"HeaderSetupLibrary": "Stel uw mediabibliotheek in",
"ButtonAddMediaFolder": "Mediamap toevoegen",
"LabelFolderType": "Maptype:",
- "MediaFolderHelpPluginRequired": "* Hiervoor is het gebruik van een Plug-in vereist, bijvoorbeeld GameBrowser of MB Bookshelf.",
"ReferToMediaLibraryWiki": "Raadpleeg de mediabibliotheek wiki.",
"LabelCountry": "Land:",
"LabelLanguage": "Taal:",
@@ -52,12 +65,16 @@
"TabPreferences": "Voorkeuren",
"TabPassword": "Wachtwoord",
"TabLibraryAccess": "Bibliotheek toegang",
+ "TabAccess": "Access",
"TabImage": "Afbeelding",
"TabProfile": "Profiel",
"TabMetadata": "Metagegevens",
"TabImages": "Afbeeldingen",
"TabNotifications": "Meldingen",
"TabCollectionTitles": "Titels",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "Toon ontbrekende afleveringen binnen een seizoen",
"LabelUnairedMissingEpisodesWithinSeasons": "Toon komende afleveringen binnen een seizoen",
"HeaderVideoPlaybackSettings": "Video afspeel instellingen",
@@ -232,10 +249,10 @@
"HeaderFeatureAccess": "Functie toegang",
"OptionAllowMediaPlayback": "Afspelen van media toestaan",
"OptionAllowBrowsingLiveTv": "Bladeren door live tv toestaan",
- "OptionAllowDeleteLibraryContent": "Allow deletion of library content",
+ "OptionAllowDeleteLibraryContent": "Verwijderen van bibliotheek inhoud toestaan",
"OptionAllowManageLiveTv": "Beheer van live tv-opnames toestaan",
- "OptionAllowRemoteControlOthers": "Allow remote control of other users",
- "OptionAllowRemoteSharedDevices": "Allow remote control of shared devices",
+ "OptionAllowRemoteControlOthers": "Op afstand besturen van andere gebruikers toestaan",
+ "OptionAllowRemoteSharedDevices": "Op afstand besturen van gedeelde apparaten toestaan",
"OptionAllowRemoteSharedDevicesHelp": "Dlna apparaten worden als gedeeld apparaat gezien totdat een gebruiker deze gaat gebruiken.",
"HeaderRemoteControl": "Gebruik op afstand",
"OptionMissingTmdbId": "TMDB Id ontbreekt",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer reel",
"OptionPlayUnwatchedTrailersOnly": "Speel alleen ongeziene trailers",
"HeaderTrailerReelHelp": "Start trailer reel om een afspeellijst met trailers af te spelen.",
- "MessageNoTrailersFound": "Geen trailers gevonden. Installeer het trailer kanaal om internet trailers te importeren.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "Nieuwe gebruikers",
"ButtonSignUp": "Aanmelden",
"ButtonForgotPassword": "Wachtwoord vergeten?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Beperk tot \u00e9\u00e9n enkele ingesloten afbeelding",
"LabelEnableSingleImageInDidlLimitHelp": "Sommige apparaten zullen niet goed weergeven als er meerdere afbeeldingen ingesloten zijn in Didl.",
"TabActivity": "Activiteit",
- "TitleSync": "Synchroniseer"
+ "TitleSync": "Synchroniseer",
+ "OptionAllowSyncContent": "Synchroniseren van media naar apparaten toestaan",
+ "NameSeasonUnknown": "Seizoen Onbekend",
+ "NameSeasonNumber": "Seizoen {0}",
+ "LabelNewUserNameHelp": "Gebruikersnamen kunnen letters (az), cijfers (0-9), streepjes, underscores (_), apostrofs (') en punten (.) bevatten\n",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/pl.json b/MediaBrowser.Server.Implementations/Localization/Server/pl.json
index a0d70f6c6..be4823a70 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/pl.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/pl.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "Asystent pomo\u017ce Ci podczas instalacji. Na pocz\u0105tku, wybierz tw\u00f3j preferowany j\u0119zyk.",
"TellUsAboutYourself": "Opowiedz nam o sobie",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "Twoje imi\u0119:",
"MoreUsersCanBeAddedLater": "Mo\u017cesz doda\u0107 wi\u0119cej u\u017cytkownik\u00f3w p\u00f3\u017aniej przez tablic\u0119 rozdzielcz\u0105.",
"UserProfilesIntro": "Media Browser posiada wbudowane wsparcie dla profili u\u017cytkownik\u00f3w, pozwalaj\u0105c ka\u017cdemu na zapisanie opcji wy\u015bwietlania, stanu odtwarzania oraz kontroli rodzicielskiej.",
@@ -37,10 +38,22 @@
"ButtonOk": "Ok",
"ButtonCancel": "Anuluj",
"ButtonNew": "New",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Movies",
+ "FolderTypeMusic": "Music",
+ "FolderTypeAdultVideos": "Adult videos",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Music videos",
+ "FolderTypeHomeVideos": "Home videos",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Books",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "Ustaw swoj\u0105 bibliotek\u0119",
"ButtonAddMediaFolder": "Dodaj folder",
"LabelFolderType": "Typ folderu:",
- "MediaFolderHelpPluginRequired": "* Wymaga u\u017cycia wtyczki takiej jak GameBrowser lub MB Bookshelf.",
"ReferToMediaLibraryWiki": "Odnie\u015b si\u0119 do wiki biblioteki.",
"LabelCountry": "Kraj:",
"LabelLanguage": "J\u0119zyk:",
@@ -52,12 +65,16 @@
"TabPreferences": "Preferencje",
"TabPassword": "Has\u0142o",
"TabLibraryAccess": "Dost\u0119p do biblioteki",
+ "TabAccess": "Access",
"TabImage": "Obraz",
"TabProfile": "Profil",
"TabMetadata": "Metadata",
"TabImages": "Images",
"TabNotifications": "Notifications",
"TabCollectionTitles": "Titles",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "Wy\u015bwietl brakuj\u0105ce odcinki w sezonach",
"LabelUnairedMissingEpisodesWithinSeasons": "Wy\u015bwietl nie wydanie odcinki w sezonach",
"HeaderVideoPlaybackSettings": "Ustawienia odtwarzania wideo",
@@ -365,8 +382,8 @@
"LabelMaxScreenshotsPerItem": "Maximum number of screenshots per item:",
"LabelMinBackdropDownloadWidth": "Minimum backdrop download width:",
"LabelMinScreenshotDownloadWidth": "Minimum screenshot download width:",
- "ButtonAddScheduledTaskTrigger": "Add Task Trigger",
- "HeaderAddScheduledTaskTrigger": "Add Task Trigger",
+ "ButtonAddScheduledTaskTrigger": "Add Trigger",
+ "HeaderAddScheduledTaskTrigger": "Add Trigger",
"ButtonAdd": "Add",
"LabelTriggerType": "Trigger Type:",
"OptionDaily": "Daily",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "New Users",
"ButtonSignUp": "Sign up",
"ButtonForgotPassword": "Forgot password?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json b/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json
index 134059faf..660f9f480 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "Este assistente ir\u00e1 gui\u00e1-lo pelo processo de instala\u00e7\u00e3o. Para come\u00e7ar, por favor selecione seu idioma preferido.",
"TellUsAboutYourself": "Conte-nos sobre voc\u00ea",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "Seu primeiro nome:",
"MoreUsersCanBeAddedLater": "Mais usu\u00e1rios poder\u00e3o ser adicionados depois dentro do Painel.",
"UserProfilesIntro": "Media Browser inclui suporte a perfis de usu\u00e1rios, permitindo que cada usu\u00e1rio tenha suas prefer\u00eancias de visualiza\u00e7\u00e3o, status das reprodu\u00e7\u00f5es e controle parental.",
@@ -37,10 +38,22 @@
"ButtonOk": "Ok",
"ButtonCancel": "Cancelar",
"ButtonNew": "Novo",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Conte\u00fado misto",
+ "FolderTypeMovies": "Filmes",
+ "FolderTypeMusic": "M\u00fasica",
+ "FolderTypeAdultVideos": "V\u00eddeos adultos",
+ "FolderTypePhotos": "Fotos",
+ "FolderTypeMusicVideos": "V\u00eddeos musicais",
+ "FolderTypeHomeVideos": "V\u00eddeos caseiros",
+ "FolderTypeGames": "Jogos",
+ "FolderTypeBooks": "Livros",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Herdar",
+ "LabelContentType": "Tipo de conte\u00fado:",
"HeaderSetupLibrary": "Configurar sua biblioteca de m\u00eddias",
"ButtonAddMediaFolder": "Adicionar pasta de m\u00eddias",
"LabelFolderType": "Tipo de pasta:",
- "MediaFolderHelpPluginRequired": "* Requer o uso de um plugin, ex. GameBrowser ou MB Bookshelf.",
"ReferToMediaLibraryWiki": "Consultar wiki da biblioteca de m\u00eddias",
"LabelCountry": "Pa\u00eds:",
"LabelLanguage": "Idioma:",
@@ -52,12 +65,16 @@
"TabPreferences": "Prefer\u00eancias",
"TabPassword": "Senha",
"TabLibraryAccess": "Acesso \u00e0 Biblioteca",
+ "TabAccess": "Acesso",
"TabImage": "Imagem",
"TabProfile": "Perfil",
"TabMetadata": "Metadados",
"TabImages": "Imagens",
"TabNotifications": "Notifica\u00e7\u00f5es",
"TabCollectionTitles": "T\u00edtulos",
+ "HeaderDeviceAccess": "Acesso ao Dispositivo",
+ "OptionEnableAccessFromAllDevices": "Ativar o acesso de todos os dispositivos",
+ "DeviceAccessHelp": "Isto apenas aplica para dispositivos que podem ser identificados como \u00fanicos e n\u00e3o evitar\u00e3o o acesso do navegador. Filtrar o acesso ao dispositivo do usu\u00e1rio evitar\u00e1 que sejam usados novos dispositivos at\u00e9 que sejam aprovados aqui.",
"LabelDisplayMissingEpisodesWithinSeasons": "Exibir epis\u00f3dios que faltam dentro das temporadas",
"LabelUnairedMissingEpisodesWithinSeasons": "Exibir epis\u00f3dios por estrear dentro das temporadas",
"HeaderVideoPlaybackSettings": "Ajustes da Reprodu\u00e7\u00e3o de V\u00eddeo",
@@ -365,8 +382,8 @@
"LabelMaxScreenshotsPerItem": "N\u00famero m\u00e1ximo de imagens de tela por item:",
"LabelMinBackdropDownloadWidth": "Tamanho m\u00ednimo da imagem de fundo para download:",
"LabelMinScreenshotDownloadWidth": "Tamanho m\u00ednimo da imagem de tela para download:",
- "ButtonAddScheduledTaskTrigger": "Adicionar Disparador da Tarefa",
- "HeaderAddScheduledTaskTrigger": "Adicionar Disparador da Tarefa",
+ "ButtonAddScheduledTaskTrigger": "Adicionar Disparador",
+ "HeaderAddScheduledTaskTrigger": "Adicionar Disparador",
"ButtonAdd": "Adicionar",
"LabelTriggerType": "Tipo de Disparador:",
"OptionDaily": "Di\u00e1rio",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Carrossel de Trailers",
"OptionPlayUnwatchedTrailersOnly": "Reproduzir apenas trailers n\u00e3o assistidos",
"HeaderTrailerReelHelp": "Inicie um carrossel de trailers para reproduzir uma longa lista de reprodu\u00e7\u00e3o de trailers.",
- "MessageNoTrailersFound": "Nenhum trailer encontrado. Instale o plugin de canal para importar uma biblioteca de trailers da internet.",
+ "MessageNoTrailersFound": "Nenhum trailer encontrado. Instale o canal Trailer para melhorar sua experi\u00eancia com filmes, adicionando uma biblioteca de trailers da internet.",
"HeaderNewUsers": "Novos Usu\u00e1rios",
"ButtonSignUp": "Entrar",
"ButtonForgotPassword": "Esqueceu a senha?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limitar a uma imagem incorporada",
"LabelEnableSingleImageInDidlLimitHelp": "Alguns dispositivos n\u00e3o interpretar\u00e3o apropriadamente se m\u00faltiplas imagens estiverem incorporadas dentro do Didl.",
"TabActivity": "Atividade",
- "TitleSync": "Sinc"
+ "TitleSync": "Sinc",
+ "OptionAllowSyncContent": "Permitir sincroniza\u00e7\u00e3o de m\u00eddia com os dispositivos",
+ "NameSeasonUnknown": "Temporada Desconhecida",
+ "NameSeasonNumber": "Temporada {0}",
+ "LabelNewUserNameHelp": "Nomes de usu\u00e1rios podem conter letras (a-z), n\u00fameros (0-9), tra\u00e7os (-), sublinhados (_), ap\u00f3strofes (') e pontos (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json b/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json
index 792a9eb0f..b9dda8704 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "Este assistente ir\u00e1 ajud\u00e1-lo durante o processo de configura\u00e7\u00e3o. Para come\u00e7ar, selecione o idioma.",
"TellUsAboutYourself": "Fale-nos sobre si",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "O seu primeiro nome:",
"MoreUsersCanBeAddedLater": "\u00c9 poss\u00edvel adicionar utilizadores mais tarde no Painel Principal",
"UserProfilesIntro": "O Media Browser inclui suporte a perfis de utilizadores, permitindo a cada utilizador ter as suas pr\u00f3prias configura\u00e7\u00f5es da visualiza\u00e7\u00e3o, estado das reprodu\u00e7\u00f5es e controlo parental.",
@@ -37,10 +38,22 @@
"ButtonOk": "Ok",
"ButtonCancel": "Cancelar",
"ButtonNew": "Novo",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Movies",
+ "FolderTypeMusic": "Music",
+ "FolderTypeAdultVideos": "Adult videos",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Music videos",
+ "FolderTypeHomeVideos": "Home videos",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Books",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "Configurar biblioteca",
"ButtonAddMediaFolder": "Adicionar pasta de media",
"LabelFolderType": "Tipo de pasta",
- "MediaFolderHelpPluginRequired": "* Requer o uso de uma extens\u00e3o, e.g. GameBrowser ou MB Bookshelf",
"ReferToMediaLibraryWiki": "Consulte a wiki",
"LabelCountry": "Pa\u00eds:",
"LabelLanguage": "Idioma:",
@@ -52,12 +65,16 @@
"TabPreferences": "Prefer\u00eancias",
"TabPassword": "Senha",
"TabLibraryAccess": "Aceder \u00e0 Biblioteca",
+ "TabAccess": "Access",
"TabImage": "Imagem",
"TabProfile": "Perfil",
"TabMetadata": "Metadados",
"TabImages": "Imagens",
"TabNotifications": "Notifica\u00e7\u00f5es",
"TabCollectionTitles": "T\u00edtulos",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "Mostrar epis\u00f3dios em falta dentro das temporadas",
"LabelUnairedMissingEpisodesWithinSeasons": "Mostrar epis\u00f3dios por estrear dentro das temporadas",
"HeaderVideoPlaybackSettings": "Configura\u00e7\u00f5es de Reprodu\u00e7\u00e3o de V\u00eddeo",
@@ -232,9 +249,9 @@
"HeaderFeatureAccess": "Acesso a Caracter\u00edsticas",
"OptionAllowMediaPlayback": "Permitir reprodu\u00e7\u00e3o de multim\u00e9dia",
"OptionAllowBrowsingLiveTv": "Permitir navega\u00e7\u00e3o da tv ao vivo",
- "OptionAllowDeleteLibraryContent": "Allow deletion of library content",
+ "OptionAllowDeleteLibraryContent": "Permitir que conte\u00fado da biblioteca seja apagado",
"OptionAllowManageLiveTv": "Permitir gest\u00e3o das grava\u00e7\u00f5es da tv ao vivo",
- "OptionAllowRemoteControlOthers": "Allow remote control of other users",
+ "OptionAllowRemoteControlOthers": "Permitir controlo remoto de outros utilizadores",
"OptionAllowRemoteSharedDevices": "Allow remote control of shared devices",
"OptionAllowRemoteSharedDevicesHelp": "Dlna devices are considered shared until a user begins controlling it.",
"HeaderRemoteControl": "Remote Control",
@@ -552,10 +569,10 @@
"MessagePleaseRestartServerToFinishUpdating": "Por favor reinicie o servidor para terminar a aplica\u00e7\u00e3o das atualiza\u00e7\u00f5es.",
"LabelDownMixAudioScale": "Escala do aumento de \u00e1udio ao fazer downmix:",
"LabelDownMixAudioScaleHelp": "Aumentar o \u00e1udio ao fazer downmix. Defina como 1 para preservar o volume original.",
- "ButtonLinkKeys": "Transfer Key",
+ "ButtonLinkKeys": "Transferir Chave",
"LabelOldSupporterKey": "Chave de apoiante antiga",
"LabelNewSupporterKey": "Chave de apoiante nova",
- "HeaderMultipleKeyLinking": "Transfer to New Key",
+ "HeaderMultipleKeyLinking": "Transferir para Nova Chave",
"MultipleKeyLinkingHelp": "If you received a new supporter key, use this form to transfer the old key's registrations to your new one.",
"LabelCurrentEmailAddress": "Endere\u00e7o de email atual",
"LabelCurrentEmailAddressHelp": "O endere\u00e7o de email atual para o qual a sua nova chave foi enviada.",
@@ -677,9 +694,9 @@
"HeaderCodecProfileHelp": "Codec profiles indicate the limitations of a device when playing specific codecs. If a limitation applies then the media will be transcoded, even if the codec is configured for direct play.",
"HeaderContainerProfile": "Container Profile",
"HeaderContainerProfileHelp": "Container profiles indicate the limitations of a device when playing specific formats. If a limitation applies then the media will be transcoded, even if the format is configured for direct play.",
- "OptionProfileVideo": "Video",
- "OptionProfileAudio": "Audio",
- "OptionProfileVideoAudio": "Video Audio",
+ "OptionProfileVideo": "V\u00eddeo",
+ "OptionProfileAudio": "\u00c1udio",
+ "OptionProfileVideoAudio": "\u00c1udio do V\u00eddeo",
"OptionProfilePhoto": "Photo",
"LabelUserLibrary": "User library:",
"LabelUserLibraryHelp": "Select which user library to display to the device. Leave empty to inherit the default setting.",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "New Users",
"ButtonSignUp": "Sign up",
"ButtonForgotPassword": "Forgot password?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ru.json b/MediaBrowser.Server.Implementations/Localization/Server/ru.json
index e35d8a097..c9f5a49e1 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/ru.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/ru.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "\u042d\u0442\u043e\u0442 \u043f\u043e\u043c\u043e\u0449\u043d\u0438\u043a \u043f\u0440\u043e\u0432\u0435\u0434\u0451\u0442 \u0432\u0430\u0441 \u0447\u0435\u0440\u0435\u0437 \u0432\u0441\u0435 \u0444\u0430\u0437\u044b \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438. \u0427\u0442\u043e\u0431\u044b \u043d\u0430\u0447\u0430\u0442\u044c, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u0432\u043e\u0439 \u043f\u0440\u0435\u0434\u043f\u043e\u0447\u0438\u0442\u0430\u0435\u043c\u044b\u0439 \u044f\u0437\u044b\u043a.",
"TellUsAboutYourself": "\u0420\u0430\u0441\u0441\u043a\u0430\u0436\u0438\u0442\u0435 \u043e \u0441\u0435\u0431\u0435",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "\u0412\u0430\u0448\u0435 \u0438\u043c\u044f:",
"MoreUsersCanBeAddedLater": "\u041f\u043e\u0442\u043e\u043c \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0435\u0449\u0451 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 \u00ab\u0418\u043d\u0444\u043e\u043f\u0430\u043d\u0435\u043b\u044c\u00bb.",
"UserProfilesIntro": "\u0412 Media Browser \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u0430 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u043f\u0440\u043e\u0444\u0438\u043b\u0435\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439, \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0449\u0430\u044f \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u0438\u0437 \u043d\u0438\u0445 \u0438\u043c\u0435\u0442\u044c \u0441\u0432\u043e\u0438 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f, \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435\u043c.",
@@ -37,10 +38,22 @@
"ButtonOk": "\u041e\u041a",
"ButtonCancel": "\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u044c",
"ButtonNew": "\u0421\u043e\u0437\u0434\u0430\u0442\u044c",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "\u0420\u0430\u0437\u043d\u043e\u0442\u0438\u043f\u043d\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435",
+ "FolderTypeMovies": "\u0424\u0438\u043b\u044c\u043c\u044b",
+ "FolderTypeMusic": "\u041c\u0443\u0437\u044b\u043a\u0430",
+ "FolderTypeAdultVideos": "\u0412\u0437\u0440\u043e\u0441\u043b\u044b\u0435 \u0432\u0438\u0434\u0435\u043e",
+ "FolderTypePhotos": "\u0424\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438",
+ "FolderTypeMusicVideos": "\u041c\u0443\u0437\u044b\u043a\u0430\u043b\u044c\u043d\u044b\u0435 \u0432\u0438\u0434\u0435\u043e",
+ "FolderTypeHomeVideos": "\u0414\u043e\u043c\u0430\u0448\u043d\u0438\u0435 \u0432\u0438\u0434\u0435\u043e",
+ "FolderTypeGames": "\u0418\u0433\u0440\u044b",
+ "FolderTypeBooks": "\u041a\u043d\u0438\u0433\u0438",
+ "FolderTypeTvShows": "\u0422\u0412",
+ "FolderTypeInherit": "\u041d\u0430\u0441\u043b\u0435\u0434\u0443\u0435\u043c\u044b\u0439",
+ "LabelContentType": "\u0422\u0438\u043f \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f:",
"HeaderSetupLibrary": "\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0438",
"ButtonAddMediaFolder": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043c\u0435\u0434\u0438\u0430\u043f\u0430\u043f\u043a\u0443",
"LabelFolderType": "\u0422\u0438\u043f \u043f\u0430\u043f\u043a\u0438:",
- "MediaFolderHelpPluginRequired": "* \u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043b\u0430\u0433\u0438\u043d, \u043d\u043f\u0440., GameBrowser \u0438\u043b\u0438 MB Bookshelf.",
"ReferToMediaLibraryWiki": "\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435\u0441\u044c \u043a \u0432\u0438\u043a\u0438 \u043f\u043e \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0435.",
"LabelCountry": "\u0421\u0442\u0440\u0430\u043d\u0430:",
"LabelLanguage": "\u042f\u0437\u044b\u043a:",
@@ -52,12 +65,16 @@
"TabPreferences": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438",
"TabPassword": "\u041f\u0430\u0440\u043e\u043b\u044c",
"TabLibraryAccess": "\u0414\u043e\u0441\u0442\u0443\u043f \u043a \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0435",
+ "TabAccess": "\u0414\u043e\u0441\u0442\u0443\u043f",
"TabImage": "\u0420\u0438\u0441\u0443\u043d\u043e\u043a",
"TabProfile": "\u041f\u0440\u043e\u0444\u0438\u043b\u044c",
"TabMetadata": "\u041c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435",
"TabImages": "\u0420\u0438\u0441\u0443\u043d\u043a\u0438",
"TabNotifications": "\u0423\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f",
"TabCollectionTitles": "\u041f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f",
+ "HeaderDeviceAccess": "\u0414\u043e\u0441\u0442\u0443\u043f \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430",
+ "OptionEnableAccessFromAllDevices": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u0441\u043e \u0432\u0441\u0435\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432",
+ "DeviceAccessHelp": "\u042d\u0442\u043e \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043e\u0434\u043d\u043e\u0437\u043d\u0430\u0447\u043d\u043e \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u043d\u044b \u0438 \u043d\u0435 \u043f\u0440\u0435\u043f\u044f\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u0443 \u0447\u0435\u0440\u0435\u0437 \u0431\u0440\u0430\u0443\u0437\u0435\u0440. \u0424\u0438\u043b\u044c\u0442\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0437\u0430\u043f\u0440\u0435\u0442\u0438\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u043f\u043e\u043a\u0430 \u043e\u043d\u0438 \u043d\u0435 \u0431\u0443\u0434\u0443\u0442 \u043e\u0434\u043e\u0431\u0440\u0435\u043d\u044b \u0442\u0443\u0442.",
"LabelDisplayMissingEpisodesWithinSeasons": "\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u044d\u043f\u0438\u0437\u043e\u0434\u044b \u0432 \u043f\u0440\u0435\u0434\u0435\u043b\u0430\u0445 \u0441\u0435\u0437\u043e\u043d\u043e\u0432",
"LabelUnairedMissingEpisodesWithinSeasons": "\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u044b\u0435 \u044d\u043f\u0438\u0437\u043e\u0434\u044b \u0432 \u043f\u0440\u0435\u0434\u0435\u043b\u0430\u0445 \u0441\u0435\u0437\u043e\u043d\u043e\u0432",
"HeaderVideoPlaybackSettings": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u0432\u0438\u0434\u0435\u043e",
@@ -225,17 +242,17 @@
"VisitMediaBrowserWebsiteLong": "\u041f\u043e\u0441\u0435\u0442\u0438\u0442\u0435 \u0441\u0430\u0439\u0442 Media Browser, \u0447\u0442\u043e\u0431\u044b \u0441\u043b\u0435\u0434\u0438\u0442\u044c \u0437\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u043c\u0438 \u043d\u043e\u0432\u043e\u0441\u0442\u044f\u043c\u0438 \u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c \u043e\u0441\u0432\u0435\u0434\u043e\u043c\u043b\u0451\u043d\u043d\u043e\u0441\u0442\u044c \u043f\u043e \u0431\u043b\u043e\u0433\u0443 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432.",
"OptionHideUser": "\u0421\u043a\u0440\u044b\u0442\u044c \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0441 \u044d\u043a\u0440\u0430\u043d\u043e\u0432 \u0432\u0445\u043e\u0434\u0430",
"OptionDisableUser": "\u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f",
- "OptionDisableUserHelp": "\u041f\u0440\u0438 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438, \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u043b\u044e\u0431\u044b\u0445 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439 \u043e\u0442 \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u0440\u0435\u0437\u043a\u043e \u043e\u0431\u043e\u0440\u0432\u0430\u043d\u044b.",
+ "OptionDisableUserHelp": "\u041f\u0440\u0438 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438, \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0430\u0435\u0442 \u043b\u044e\u0431\u044b\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u043e\u0442 \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u0440\u0435\u0437\u043a\u043e \u043e\u0431\u043e\u0440\u0432\u0430\u043d\u044b.",
"HeaderAdvancedControl": "\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u043e\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435",
"LabelName": "\u0418\u043c\u044f (\u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435):",
- "OptionAllowUserToManageServer": "\u042d\u0442\u043e\u043c\u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c",
+ "OptionAllowUserToManageServer": "\u042d\u0442\u043e\u043c\u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u0440\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c",
"HeaderFeatureAccess": "\u0414\u043e\u0441\u0442\u0443\u043f \u043a \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438",
"OptionAllowMediaPlayback": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043c\u0435\u0434\u0438\u0430\u0434\u0430\u043d\u043d\u044b\u0445",
"OptionAllowBrowsingLiveTv": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440 \u0422\u0412-\u044d\u0444\u0438\u0440\u0430",
- "OptionAllowDeleteLibraryContent": "\u041f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0438",
+ "OptionAllowDeleteLibraryContent": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0438",
"OptionAllowManageLiveTv": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0437\u0430\u043f\u0438\u0441\u044f\u043c\u0438 \u0441 \u0422\u0412-\u044d\u0444\u0438\u0440\u0430",
- "OptionAllowRemoteControlOthers": "\u041f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0451\u043d\u043d\u043e\u0433\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u0440\u0443\u0433\u0438\u043c\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c\u0438",
- "OptionAllowRemoteSharedDevices": "\u041f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0451\u043d\u043d\u043e\u0433\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043e\u0431\u0449\u0438\u043c\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438",
+ "OptionAllowRemoteControlOthers": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0443\u0434\u0430\u043b\u0451\u043d\u043d\u043e\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c\u0438",
+ "OptionAllowRemoteSharedDevices": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0443\u0434\u0430\u043b\u0451\u043d\u043d\u043e\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0431\u0449\u0438\u043c\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438",
"OptionAllowRemoteSharedDevicesHelp": "DLNA-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0441\u0447\u0438\u0442\u0430\u044e\u0442\u0441\u044f \u043e\u0431\u0449\u0438\u043c\u0438, \u043f\u043e\u043a\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0438\u043c\u0438.",
"HeaderRemoteControl": "\u0423\u0434\u0430\u043b\u0451\u043d\u043d\u043e\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435",
"OptionMissingTmdbId": "\u041d\u0435\u0442 TMDb Id",
@@ -258,7 +275,7 @@
"OptionRelease": "\u041e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u0432\u044b\u043f\u0443\u0441\u043a",
"OptionBeta": "\u0411\u0435\u0442\u0430-\u0432\u0435\u0440\u0441\u0438\u044f",
"OptionDev": "\u0420\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c\u0430\u044f (\u043d\u0435\u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e)",
- "LabelAllowServerAutoRestart": "\u0421\u0435\u0440\u0432\u0435\u0440\u0443 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438, \u0447\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0430\u043b\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0434\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439",
+ "LabelAllowServerAutoRestart": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440\u0443 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a \u0434\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439",
"LabelAllowServerAutoRestartHelp": "\u0421\u0435\u0440\u0432\u0435\u0440 \u0431\u0443\u0434\u0435\u0442 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u043f\u0435\u0440\u0438\u043e\u0434\u044b \u043f\u0440\u043e\u0441\u0442\u043e\u044f, \u043a\u043e\u0433\u0434\u0430 \u043d\u0438\u043a\u0430\u043a\u0438\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u043d\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u044b.",
"LabelEnableDebugLogging": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u044b\u0435 \u0437\u0430\u043f\u0438\u0441\u0438 \u0432 \u0416\u0443\u0440\u043d\u0430\u043b\u0435",
"LabelRunServerAtStartup": "\u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440 \u043f\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b",
@@ -365,8 +382,8 @@
"LabelMaxScreenshotsPerItem": "\u041c\u0430\u043a\u0441. \u0447\u0438\u0441\u043b\u043e \u0441\u043d\u0438\u043c\u043a\u043e\u0432 \u044d\u043a\u0440\u0430\u043d\u0430 \u043d\u0430 \u044d\u043b\u0435\u043c\u0435\u043d\u0442:",
"LabelMinBackdropDownloadWidth": "\u041c\u0438\u043d. \u0448\u0438\u0440\u0438\u043d\u0430 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c\u043e\u0433\u043e \u0437\u0430\u0434\u043d\u0438\u043a\u0430:",
"LabelMinScreenshotDownloadWidth": "\u041c\u0438\u043d. \u0448\u0438\u0440\u0438\u043d\u0430 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c\u043e\u0433\u043e \u0441\u043d\u0438\u043c\u043a\u0430 \u044d\u043a\u0440\u0430\u043d\u0430:",
- "ButtonAddScheduledTaskTrigger": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u0440\u0438\u0433\u0433\u0435\u0440 \u0437\u0430\u0434\u0430\u0447\u0438",
- "HeaderAddScheduledTaskTrigger": "\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0442\u0440\u0438\u0433\u0433\u0435\u0440\u0430 \u0437\u0430\u0434\u0430\u0447\u0438",
+ "ButtonAddScheduledTaskTrigger": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u0440\u0438\u0433\u0433\u0435\u0440",
+ "HeaderAddScheduledTaskTrigger": "\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0442\u0440\u0438\u0433\u0433\u0435\u0440\u0430",
"ButtonAdd": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c",
"LabelTriggerType": "\u0422\u0438\u043f \u0442\u0440\u0438\u0433\u0433\u0435\u0440\u0430:",
"OptionDaily": "\u0415\u0436\u0435\u0434\u043d\u0435\u0432\u043d\u043e",
@@ -435,7 +452,7 @@
"OptionMaxQualityTranscoding": "\u041a\u0430\u0447\u0435\u0441\u0442\u0432\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e",
"OptionEnableDebugTranscodingLogging": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u044b\u0435 \u0437\u0430\u043f\u0438\u0441\u0438 \u043f\u0435\u0440\u0435\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0438 \u0432 \u0416\u0443\u0440\u043d\u0430\u043b\u0435",
"OptionEnableDebugTranscodingLoggingHelp": "\u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u0431\u0443\u0434\u0443\u0442 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c\u0441\u044f \u0444\u0430\u0439\u043b\u044b \u0416\u0443\u0440\u043d\u0430\u043b\u0430 \u043e\u0447\u0435\u043d\u044c \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u043e\u0431\u044a\u0451\u043c\u0430, \u0430 \u044d\u0442\u043e \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0441\u0438\u043b\u0443 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u0443\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u043d\u0435\u043f\u043e\u043b\u0430\u0434\u043e\u043a.",
- "OptionUpscaling": "\u041a\u043b\u0438\u0435\u043d\u0442\u0430\u043c \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u0437\u0430\u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u044b\u0441\u043e\u043a\u043e\u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0433\u043e \u0432\u0438\u0434\u0435\u043e",
+ "OptionUpscaling": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043c \u0437\u0430\u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u0435 \u0432\u044b\u0441\u043e\u043a\u043e\u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0433\u043e \u0432\u0438\u0434\u0435\u043e",
"OptionUpscalingHelp": "\u0412 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445, \u044d\u0442\u043e \u043f\u0440\u0438\u0432\u0435\u0434\u0451\u0442 \u043a \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u044e \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u0432\u0438\u0434\u0435\u043e, \u043d\u043e \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0442\u0441\u044f \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440\u0430.",
"EditCollectionItemsHelp": "\u0414\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0438\u043b\u0438 \u0438\u0437\u044b\u043c\u0438\u0442\u0435 \u043b\u044e\u0431\u044b\u0435 \u0444\u0438\u043b\u044c\u043c\u044b, \u0441\u0435\u0440\u0438\u0430\u043b\u044b, \u0430\u043b\u044c\u0431\u043e\u043c\u044b, \u043a\u043d\u0438\u0433\u0438 \u0438\u043b\u0438 \u0438\u0433\u0440\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0445\u043e\u0442\u0438\u0442\u0435 \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u043d\u0443\u0442\u0440\u0438 \u0434\u0430\u043d\u043d\u043e\u0439 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438.",
"HeaderAddTitles": "\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0439",
@@ -690,7 +707,7 @@
"LabelSupportedMediaTypes": "\u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0435 \u0442\u0438\u043f\u044b \u043c\u0435\u0434\u0438\u0430\u0434\u0430\u043d\u043d\u044b\u0445:",
"TabIdentification": "\u0420\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u043d\u0438\u0435",
"HeaderIdentification": "\u0420\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u043d\u0438\u0435",
- "TabDirectPlay": "\u041f\u0440\u044f\u043c\u043e\u0435 \u0432\u043e\u0441\u043f\u0440-\u0438\u0435",
+ "TabDirectPlay": "\u041f\u0440\u044f\u043c\u043e\u0435 \u0432\u043e\u0441\u043f\u0440.",
"TabContainers": "\u041a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u044b",
"TabCodecs": "\u041a\u043e\u0434\u0435\u043a\u0438",
"TabResponses": "\u041e\u0442\u043a\u043b\u0438\u043a\u0438",
@@ -944,7 +961,7 @@
"TabSort": "\u041f\u043e\u0440\u044f\u0434\u043e\u043a",
"TabFilter": "\u0424\u0438\u043b\u044c\u0442\u0440\u044b",
"ButtonView": "\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c",
- "LabelPageSize": "\u041c\u0430\u043a\u0441. \u0447\u0438\u0441\u043b\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432:",
+ "LabelPageSize": "\u041f\u0440\u0435\u0434\u0435\u043b \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432:",
"LabelPath": "\u041f\u0443\u0442\u044c:",
"LabelView": "\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435:",
"TabUsers": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438",
@@ -1021,9 +1038,9 @@
"ItemAddedWithName": "{0} (\u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0443)",
"ItemRemovedWithName": "{0} (\u0438\u0437\u044a\u044f\u0442\u043e \u0438\u0437 \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0438)",
"DeviceOnlineWithName": "{0} - \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e",
- "UserOnlineFromDevice": "{0} - \u043f\u043e\u0434\u043a\u043b-\u0438\u0435 \u0441 {1} \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e",
+ "UserOnlineFromDevice": "{0} - \u043f\u043e\u0434\u043a\u043b. \u0441 {1} \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e",
"DeviceOfflineWithName": "{0} - \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043f\u0440\u0435\u0440\u0432\u0430\u043d\u043e",
- "UserOfflineFromDevice": "{0} - \u043f\u043e\u0434\u043a\u043b-\u0438\u0435 \u0441 {1} \u043f\u0440\u0435\u0440\u0432\u0430\u043d\u043e",
+ "UserOfflineFromDevice": "{0} - \u043f\u043e\u0434\u043a\u043b. \u0441 {1} \u043f\u0440\u0435\u0440\u0432\u0430\u043d\u043e",
"SubtitlesDownloadedForItem": "\u0421\u0443\u0431\u0442\u0438\u0442\u0440\u044b \u0434\u043b\u044f {0} \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u043b\u0438\u0441\u044c",
"SubtitleDownloadFailureForItem": "\u0421\u0443\u0431\u0442\u0438\u0442\u0440\u044b \u043a {0} \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c",
"LabelRunningTimeValue": "\u0412\u0440\u0435\u043c\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f: {0}",
@@ -1037,9 +1054,9 @@
"MessageApplicationUpdated": "Media Browser Server \u0431\u044b\u043b \u043e\u0431\u043d\u043e\u0432\u043b\u0451\u043d",
"AuthenticationSucceededWithUserName": "{0} - \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u0430",
"FailedLoginAttemptWithUserName": "{0} - \u043f\u043e\u043f\u044b\u0442\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 \u043d\u0435\u0443\u0434\u0430\u0447\u043d\u0430",
- "UserStartedPlayingItemWithValues": "{0} - \u0432\u043e\u0441\u043f\u0440-\u0438\u0435 \u00ab{1}\u00bb \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043e",
- "UserStoppedPlayingItemWithValues": "{0} - \u0432\u043e\u0441\u043f\u0440-\u0438\u0435 \u00ab{1}\u00bb \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e",
- "AppDeviceValues": "\u041f\u0440\u0438\u043b-\u0438\u0435: {0}, \u0423\u0441\u0442\u0440-\u0432\u043e: {1}",
+ "UserStartedPlayingItemWithValues": "{0} - \u0432\u043e\u0441\u043f\u0440. \u00ab{1}\u00bb \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043e",
+ "UserStoppedPlayingItemWithValues": "{0} - \u0432\u043e\u0441\u043f\u0440. \u00ab{1}\u00bb \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e",
+ "AppDeviceValues": "\u041f\u0440\u0438\u043b.: {0}, \u0423\u0441\u0442\u0440.: {1}",
"ProviderValue": "\u041f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a: {0}",
"LabelChannelDownloadSizeLimit": "\u041e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c\u043e\u0433\u043e, \u0413\u0411:",
"LabelChannelDownloadSizeLimitHelpText": "\u041e\u0433\u0440\u0430\u043d\u0438\u0447\u044c\u0442\u0435 \u0440\u0430\u0437\u043c\u0435\u0440 \u043f\u0430\u043f\u043a\u0438 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c\u044b\u0445 \u043a\u0430\u043d\u0430\u043b\u043e\u0432.",
@@ -1191,8 +1208,8 @@
"LabelLimitIntrosToUnwatchedContent": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u044b \u0442\u043e\u043b\u044c\u043a\u043e \u043a \u043d\u0435\u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u043d\u043e\u043c\u0443 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044e",
"LabelEnableIntroParentalControl": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0438\u043d\u0442\u0435\u043b\u043b\u0435\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435\u043c",
"LabelEnableIntroParentalControlHelp": "\u0422\u0440\u0435\u0439\u043b\u0435\u0440\u044b \u0431\u0443\u0434\u0443\u0442 \u0432\u044b\u0431\u0438\u0440\u0430\u0442\u044c\u0441\u044f \u0441 \u0432\u043e\u0437\u0440\u0430\u0441\u0442\u043d\u043e\u0439 \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u0435\u0439 \u0440\u0430\u0432\u043d\u043e\u0439 \u0438\u043b\u0438 \u043c\u0435\u043d\u044c\u0448\u0435\u0439, \u0447\u0435\u043c \u043f\u0440\u043e\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u0435\u043c\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435.",
- "LabelTheseFeaturesRequireSupporterHelpAndTrailers": "\u0414\u0430\u043d\u043d\u044b\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u043c \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0447\u043b\u0435\u043d\u0441\u0442\u0432\u043e \u0441\u043f\u043e\u043d\u0441\u043e\u0440\u0430 \u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u043a\u0430\u043d\u0430\u043b\u0430 \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043e\u0432.",
- "OptionTrailersFromMyMoviesHelp": "\u041f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0445 \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043e\u0432.",
+ "LabelTheseFeaturesRequireSupporterHelpAndTrailers": "\u0414\u0430\u043d\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0442\u0440\u0435\u0431\u0443\u044e\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0447\u043b\u0435\u043d\u0441\u0442\u0432\u0430 \u0441\u043f\u043e\u043d\u0441\u043e\u0440\u0430 \u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u043a\u0430\u043d\u0430\u043b\u0430 \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043e\u0432.",
+ "OptionTrailersFromMyMoviesHelp": "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0445 \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043e\u0432.",
"LabelCustomIntrosPath": "\u041f\u0443\u0442\u044c \u043a \u043d\u0435\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u043c \u0437\u0430\u0441\u0442\u0430\u0432\u043a\u0430\u043c:",
"LabelCustomIntrosPathHelp": "\u041f\u0430\u043f\u043a\u0430, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0430\u044f \u0432\u0438\u0434\u0435\u043e\u0444\u0430\u0439\u043b\u044b. \u0412\u0438\u0434\u0435\u043e \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u043e \u0432\u044b\u0431\u0440\u0430\u043d\u043e \u0438 \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u043f\u043e\u0441\u043b\u0435 \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043e\u0432.",
"ValueSpecialEpisodeName": "\u0421\u043f\u0435\u0446\u044d\u043f\u0438\u0437\u043e\u0434 - {0}",
@@ -1200,7 +1217,7 @@
"OptionUpcomingDvdMovies": "\u041e\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u044b \u043a \u043d\u043e\u0432\u044b\u043c \u0438 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u044b\u043c \u0444\u0438\u043b\u044c\u043c\u0430\u043c \u043d\u0430 DVD \u0438 BluRay",
"OptionUpcomingStreamingMovies": "\u041e\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u044b \u043a \u043d\u043e\u0432\u044b\u043c \u0438 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u044b\u043c \u0444\u0438\u043b\u044c\u043c\u0430\u043c \u043d\u0430 Netflix",
"LabelDisplayTrailersWithinMovieSuggestions": "\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u044b \u0432 \u043f\u0440\u0435\u0434\u0435\u043b\u0430\u0445 \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0435\u043c\u044b\u0445 \u0444\u0438\u043b\u044c\u043c\u043e\u0432",
- "LabelDisplayTrailersWithinMovieSuggestionsHelp": "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043a\u0430\u043d\u0430\u043b\u0430 \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043e\u0432",
+ "LabelDisplayTrailersWithinMovieSuggestionsHelp": "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043a\u0430\u043d\u0430\u043b\u0430 \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043e\u0432.",
"CinemaModeConfigurationHelp2": "\u041e\u0431\u043e\u0441\u043e\u0431\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u0431\u0443\u0434\u0443\u0442 \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u044b \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u0440\u0435\u0436\u0438\u043c \u043a\u0438\u043d\u043e\u0442\u0435\u0430\u0442\u0440\u0430 \u0432 \u043f\u0440\u0435\u0434\u0435\u043b\u0430\u0445 \u0441\u0432\u043e\u0438\u0445 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0445 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a.",
"LabelEnableCinemaMode": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0440\u0435\u0436\u0438\u043c \u043a\u0438\u043d\u043e\u0442\u0435\u0430\u0442\u0440\u0430",
"HeaderCinemaMode": "\u0420\u0435\u0436\u0438\u043c \u043a\u0438\u043d\u043e\u0442\u0435\u0430\u0442\u0440\u0430",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "\u0421\u043a\u043b\u0435\u0439\u043a\u0430 \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043e\u0432",
"OptionPlayUnwatchedTrailersOnly": "\u0412\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0435\u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u043d\u044b\u0435 \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u044b",
"HeaderTrailerReelHelp": "\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 \u0441\u043a\u043b\u0435\u0439\u043a\u0443 \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043e\u0432, \u0447\u0442\u043e\u0431\u044b \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0441\u0442\u0438 \u0434\u043e\u043b\u0433\u043e\u0438\u0433\u0440\u0430\u044e\u0449\u0438\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043e\u0432.",
- "MessageNoTrailersFound": "\u0422\u0440\u0435\u0439\u043b\u0435\u0440\u043e\u0432 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e. \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 \u043f\u043b\u0430\u0433\u0438\u043d \u043a\u0430\u043d\u0430\u043b\u0430 \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043e\u0432 \u0434\u043b\u044f \u0438\u043c\u043f\u043e\u0440\u0442\u0430 \u0441\u043e\u0431\u0440\u0430\u043d\u0438\u044f \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442-\u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043e\u0432.",
+ "MessageNoTrailersFound": "\u0422\u0440\u0435\u0439\u043b\u0435\u0440\u044b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b. \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 \u043a\u0430\u043d\u0430\u043b \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043e\u0432, \u0447\u0442\u043e\u0431\u044b \u0443\u0441\u0438\u043b\u0438\u0442\u044c \u0432\u0430\u0448\u0435 \u0432\u043f\u0435\u0447\u0430\u0442\u043b\u0435\u043d\u0438\u0435 \u043e\u0442 \u0444\u0438\u043b\u044c\u043c\u0430, \u043f\u0443\u0442\u0435\u043c \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u0431\u0440\u0430\u043d\u0438\u044f \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442-\u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043e\u0432.",
"HeaderNewUsers": "\u041d\u043e\u0432\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438",
"ButtonSignUp": "\u0417\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f",
"ButtonForgotPassword": "\u0417\u0430\u0431\u044b\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c?",
@@ -1277,7 +1294,13 @@
"LabelBlockItemsWithTags": "\u0411\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0441 \u0442\u0435\u0433\u0430\u043c\u0438:",
"LabelTag": "\u0422\u0435\u0433:",
"LabelEnableSingleImageInDidlLimit": "\u041e\u0433\u0440\u0430\u043d\u0438\u0447\u0438\u0442\u044c \u0434\u043e \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0433\u043e \u0432\u043d\u0435\u0434\u0440\u0451\u043d\u043d\u043e\u0433\u043e \u0440\u0438\u0441\u0443\u043d\u043a\u0430",
- "LabelEnableSingleImageInDidlLimitHelp": "\u041d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u0431\u0443\u0434\u0443\u0442 \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e, \u0435\u0441\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0440\u0438\u0441\u0443\u043d\u043a\u043e\u0432 \u0432\u043d\u0435\u0434\u0440\u044f\u044e\u0442\u0441\u044f \u0432\u043d\u0443\u0442\u0440\u0438 Didl.",
+ "LabelEnableSingleImageInDidlLimitHelp": "\u041d\u0430 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0445 \u043d\u0435 \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e, \u0435\u0441\u043b\u0438 \u0432\u043d\u0435\u0434\u0440\u0435\u043d\u044b \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0440\u0438\u0441\u0443\u043d\u043a\u043e\u0432 \u0432\u043d\u0443\u0442\u0440\u0438 Didl.",
"TabActivity": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044f",
- "TitleSync": "\u0421\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u044f"
+ "TitleSync": "\u0421\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u044f",
+ "OptionAllowSyncContent": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u044e \u043c\u0435\u0434\u0438\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438",
+ "NameSeasonUnknown": "\u0421\u0435\u0437\u043e\u043d \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u0435\u043d",
+ "NameSeasonNumber": "\u0421\u0435\u0437\u043e\u043d {0}",
+ "LabelNewUserNameHelp": "\u0418\u043c\u0435\u043d\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u043c\u043e\u0433\u0443\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0431\u0443\u043a\u0432\u044b (a-z), \u0446\u0438\u0444\u0440\u044b (0-9), \u0434\u0435\u0444\u0438\u0441\u044b (-), \u043f\u043e\u0434\u0447\u0451\u0440\u043a\u0438\u0432\u0430\u043d\u0438\u044f (_), \u0430\u043f\u043e\u0441\u0442\u0440\u043e\u0444\u044b (') \u0438 \u0442\u043e\u0447\u043a\u0438 (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json
index 09cd286b4..2d00f3c76 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/server.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "This wizard will help guide you through the setup process. To begin, please select your preferred language.",
"TellUsAboutYourself": "Tell us about yourself",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "Your first name:",
"MoreUsersCanBeAddedLater": "More users can be added later within the Dashboard.",
"UserProfilesIntro": "Media Browser includes built-in support for user profiles, enabling each user to have their own display settings, playstate and parental controls.",
@@ -37,10 +38,22 @@
"ButtonOk": "Ok",
"ButtonCancel": "Cancel",
"ButtonNew": "New",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Movies",
+ "FolderTypeMusic": "Music",
+ "FolderTypeAdultVideos": "Adult videos",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Music videos",
+ "FolderTypeHomeVideos": "Home videos",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Books",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "Setup your media library",
"ButtonAddMediaFolder": "Add media folder",
"LabelFolderType": "Folder type:",
- "MediaFolderHelpPluginRequired": "* Requires the use of a plugin, e.g. GameBrowser or MB Bookshelf.",
"ReferToMediaLibraryWiki": "Refer to the media library wiki.",
"LabelCountry": "Country:",
"LabelLanguage": "Language:",
@@ -52,12 +65,16 @@
"TabPreferences": "Preferences",
"TabPassword": "Password",
"TabLibraryAccess": "Library Access",
+ "TabAccess": "Access",
"TabImage": "Image",
"TabProfile": "Profile",
"TabMetadata": "Metadata",
"TabImages": "Images",
"TabNotifications": "Notifications",
"TabCollectionTitles": "Titles",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "Display missing episodes within seasons",
"LabelUnairedMissingEpisodesWithinSeasons": "Display unaired episodes within seasons",
"HeaderVideoPlaybackSettings": "Video Playback Settings",
@@ -224,10 +241,12 @@
"VisitMediaBrowserWebsite": "Visit the Media Browser Web Site",
"VisitMediaBrowserWebsiteLong": "Visit the Media Browser Web site to catch the latest news and keep up with the developer blog.",
"OptionHideUser": "Hide this user from login screens",
+ "OptionHideUserFromLoginHelp": "Useful for private or hidden administrator accounts. The user will need to sign in manually by entering their username and password.",
"OptionDisableUser": "Disable this user",
"OptionDisableUserHelp": "If disabled the server will not allow any connections from this user. Existing connections will be abruptly terminated.",
"HeaderAdvancedControl": "Advanced Control",
"LabelName": "Name:",
+ "ButtonHelp": "Help",
"OptionAllowUserToManageServer": "Allow this user to manage the server",
"HeaderFeatureAccess": "Feature Access",
"OptionAllowMediaPlayback": "Allow media playback",
@@ -366,8 +385,8 @@
"LabelMaxScreenshotsPerItem": "Maximum number of screenshots per item:",
"LabelMinBackdropDownloadWidth": "Minimum backdrop download width:",
"LabelMinScreenshotDownloadWidth": "Minimum screenshot download width:",
- "ButtonAddScheduledTaskTrigger": "Add Task Trigger",
- "HeaderAddScheduledTaskTrigger": "Add Task Trigger",
+ "ButtonAddScheduledTaskTrigger": "Add Trigger",
+ "HeaderAddScheduledTaskTrigger": "Add Trigger",
"ButtonAdd": "Add",
"LabelTriggerType": "Trigger Type:",
"OptionDaily": "Daily",
@@ -1262,7 +1281,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "New Users",
"ButtonSignUp": "Sign up",
"ButtonForgotPassword": "Forgot password?",
@@ -1295,5 +1314,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
}
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/sv.json b/MediaBrowser.Server.Implementations/Localization/Server/sv.json
index b3831a097..8a68f418e 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/sv.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/sv.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "Den h\u00e4r guiden hj\u00e4lper dig att g\u00f6ra de f\u00f6rsta inst\u00e4llningarna. F\u00f6r att b\u00f6rja var v\u00e4nlig v\u00e4lj \u00f6nskat spr\u00e5k.",
"TellUsAboutYourself": "Ber\u00e4tta om dig sj\u00e4lv",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "Ditt f\u00f6rnamn:",
"MoreUsersCanBeAddedLater": "Flera anv\u00e4ndare kan skapas senare i Kontrollpanelen.",
"UserProfilesIntro": "Media Browser har inbyggt st\u00f6d f\u00f6r anv\u00e4ndarprofiler, s\u00e5 varje anv\u00e4ndare kan ha sina egna utseendeinst\u00e4llningar, visad-markeringar och f\u00f6r\u00e4ldral\u00e5s.",
@@ -37,10 +38,22 @@
"ButtonOk": "OK",
"ButtonCancel": "Avbryt",
"ButtonNew": "Nytillkommet",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Filmer",
+ "FolderTypeMusic": "Musik",
+ "FolderTypeAdultVideos": "Inneh\u00e5ll f\u00f6r vuxna",
+ "FolderTypePhotos": "Foton",
+ "FolderTypeMusicVideos": "Musikvideor",
+ "FolderTypeHomeVideos": "Hemvideor",
+ "FolderTypeGames": "Spel",
+ "FolderTypeBooks": "B\u00f6cker",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "Konfigurera mediabiblioteket",
"ButtonAddMediaFolder": "Skapa mediamapp",
"LabelFolderType": "Typ av mapp:",
- "MediaFolderHelpPluginRequired": "* Kr\u00e4ver att ett till\u00e4gg, t ex GameBrowser eller MB Bookshelf, \u00e4r installerat.",
"ReferToMediaLibraryWiki": "Se avsnittet om mediabibliotek i v\u00e5r Wiki.",
"LabelCountry": "Land:",
"LabelLanguage": "Spr\u00e5k:",
@@ -52,12 +65,16 @@
"TabPreferences": "Inst\u00e4llningar",
"TabPassword": "L\u00f6senord",
"TabLibraryAccess": "\u00c5tkomst till biblioteket",
+ "TabAccess": "Access",
"TabImage": "Bild",
"TabProfile": "Profil",
"TabMetadata": "Metadata",
"TabImages": "Bilder",
"TabNotifications": "Meddelanden",
"TabCollectionTitles": "Titlar",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "Visa saknade avsnitt i s\u00e4songer",
"LabelUnairedMissingEpisodesWithinSeasons": "Visa \u00e4nnu ej s\u00e4nda avsnitt i s\u00e4songer",
"HeaderVideoPlaybackSettings": "Inst\u00e4llningar f\u00f6r videouppspelning",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "New Users",
"ButtonSignUp": "Sign up",
"ButtonForgotPassword": "Forgot password?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/tr.json b/MediaBrowser.Server.Implementations/Localization/Server/tr.json
index 51644f389..86b835650 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/tr.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/tr.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Taray\u0131c\u0131",
"ThisWizardWillGuideYou": "Bu sihirbaz kurulum i\u015flemi boyunca size yard\u0131mc\u0131 olacakt\u0131r. Ba\u015flamak i\u00e7in, tercih etti\u011finiz dili se\u00e7iniz.",
"TellUsAboutYourself": "Kendinizden Bahsedin",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "\u0130lk Ad",
"MoreUsersCanBeAddedLater": "More users can be added later within the Dashboard.",
"UserProfilesIntro": "Media Browser includes built-in support for user profiles, enabling each user to have their own display settings, playstate and parental controls.",
@@ -37,10 +38,22 @@
"ButtonOk": "Tamam",
"ButtonCancel": "\u0130ptal",
"ButtonNew": "Yeni",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Movies",
+ "FolderTypeMusic": "Music",
+ "FolderTypeAdultVideos": "Adult videos",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Music videos",
+ "FolderTypeHomeVideos": "Home videos",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Books",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "Medya k\u00fct\u00fcphaneni kur",
"ButtonAddMediaFolder": "Yeni Media Klas\u00f6r\u00fc",
"LabelFolderType": "Klas\u00f6r T\u00fcr\u00fc:",
- "MediaFolderHelpPluginRequired": "* Requires the use of a plugin, e.g. GameBrowser or MB Bookshelf.",
"ReferToMediaLibraryWiki": "Refer to the media library wiki.",
"LabelCountry": "\u00dclke",
"LabelLanguage": "Dil",
@@ -52,12 +65,16 @@
"TabPreferences": "Tercihler",
"TabPassword": "\u015eifre",
"TabLibraryAccess": "K\u00fct\u00fcphane Eri\u015fim",
+ "TabAccess": "Access",
"TabImage": "Resim",
"TabProfile": "Profil",
"TabMetadata": "Metadata",
"TabImages": "Resimler",
"TabNotifications": "Notifications",
"TabCollectionTitles": "Titles",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "Sezondaki kay\u0131p b\u00f6l\u00fcmleri g\u00f6ster",
"LabelUnairedMissingEpisodesWithinSeasons": "Display unaired episodes within seasons",
"HeaderVideoPlaybackSettings": "Video Oynatma Ayarlar\u0131",
@@ -365,8 +382,8 @@
"LabelMaxScreenshotsPerItem": "Maximum number of screenshots per item:",
"LabelMinBackdropDownloadWidth": "Minimum backdrop download width:",
"LabelMinScreenshotDownloadWidth": "Minimum screenshot download width:",
- "ButtonAddScheduledTaskTrigger": "Add Task Trigger",
- "HeaderAddScheduledTaskTrigger": "Add Task Trigger",
+ "ButtonAddScheduledTaskTrigger": "Add Trigger",
+ "HeaderAddScheduledTaskTrigger": "Add Trigger",
"ButtonAdd": "Ekle",
"LabelTriggerType": "Trigger Type:",
"OptionDaily": "G\u00fcnl\u00fck",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "New Users",
"ButtonSignUp": "Sign up",
"ButtonForgotPassword": "Forgot password?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/vi.json b/MediaBrowser.Server.Implementations/Localization/Server/vi.json
index 93a725d19..ee9d060bc 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/vi.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/vi.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "Th\u1ee7 thu\u1eadt n\u00e0y s\u1ebd h\u01b0\u1edbng d\u1eabn qu\u00e1 tr\u00ecnh c\u00e0i \u0111\u1eb7t cho b\u1ea1n. \u0110\u1ec3 b\u1eaft \u0111\u1ea7u, vui l\u00f2ng l\u1ef1a ch\u1ecdn ng\u00f4n ng\u1eef b\u1ea1n \u01b0a th\u00edch.",
"TellUsAboutYourself": "N\u00f3i cho ch\u00fang t\u00f4i bi\u1ebft \u0111\u00f4i \u0111i\u1ec1u v\u1ec1 B\u1ea1n",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "T\u00ean c\u1ee7a B\u1ea1n",
"MoreUsersCanBeAddedLater": "More users can be added later within the Dashboard.",
"UserProfilesIntro": "Media Browser includes built-in support for user profiles, enabling each user to have their own display settings, playstate and parental controls.",
@@ -37,10 +38,22 @@
"ButtonOk": "Ok",
"ButtonCancel": "Tho\u00e1t",
"ButtonNew": "M\u1edbi",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Movies",
+ "FolderTypeMusic": "Music",
+ "FolderTypeAdultVideos": "Adult videos",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Music videos",
+ "FolderTypeHomeVideos": "Home videos",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Books",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "C\u00e0i \u0111\u1eb7t th\u01b0 vi\u1ec7n media c\u1ee7a b\u1ea1n",
"ButtonAddMediaFolder": "Th\u00eam m\u1ed9t th\u01b0 m\u1ee5c media",
"LabelFolderType": "Lo\u1ea1i th\u01b0 m\u1ee5c",
- "MediaFolderHelpPluginRequired": "* Requires the use of a plugin, e.g. GameBrowser or MB Bookshelf.",
"ReferToMediaLibraryWiki": "Tham kh\u1ea3o th\u01b0 vi\u1ec7n wiki media.",
"LabelCountry": "Qu\u1ed1c gia:",
"LabelLanguage": "Ng\u00f4n ng\u1eef",
@@ -52,12 +65,16 @@
"TabPreferences": "\u01afa th\u00edch",
"TabPassword": "M\u1eadt kh\u1ea9u",
"TabLibraryAccess": "Truy c\u1eadp th\u01b0 vi\u1ec7n",
+ "TabAccess": "Access",
"TabImage": "H\u00ecnh \u1ea3nh",
"TabProfile": "H\u1ed3 s\u01a1",
"TabMetadata": "Metadata",
"TabImages": "H\u00ecnh \u1ea3nh",
"TabNotifications": "Notifications",
"TabCollectionTitles": "Ti\u00eau \u0111\u1ec1",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "Display missing episodes within seasons",
"LabelUnairedMissingEpisodesWithinSeasons": "Display unaired episodes within seasons",
"HeaderVideoPlaybackSettings": "C\u00e1c c\u00e0i \u0111\u1eb7t ph\u00e1t Video",
@@ -365,8 +382,8 @@
"LabelMaxScreenshotsPerItem": "Maximum number of screenshots per item:",
"LabelMinBackdropDownloadWidth": "Minimum backdrop download width:",
"LabelMinScreenshotDownloadWidth": "Minimum screenshot download width:",
- "ButtonAddScheduledTaskTrigger": "Add Task Trigger",
- "HeaderAddScheduledTaskTrigger": "Add Task Trigger",
+ "ButtonAddScheduledTaskTrigger": "Add Trigger",
+ "HeaderAddScheduledTaskTrigger": "Add Trigger",
"ButtonAdd": "Th\u00eam",
"LabelTriggerType": "Trigger Type:",
"OptionDaily": "Daily",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "New Users",
"ButtonSignUp": "Sign up",
"ButtonForgotPassword": "Forgot password?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/zh_CN.json b/MediaBrowser.Server.Implementations/Localization/Server/zh_CN.json
index bfc16efb8..d34eacd58 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/zh_CN.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/zh_CN.json
@@ -1,5 +1,5 @@
{
- "LabelExit": "\u79bb\u5f00",
+ "LabelExit": "\u9000\u51fa",
"LabelVisitCommunity": "\u8bbf\u95ee\u793e\u533a",
"LabelGithub": "Github",
"LabelSwagger": "Swagger",
@@ -7,40 +7,53 @@
"LabelApiDocumentation": "Api Documentation",
"LabelDeveloperResources": "Developer Resources",
"LabelBrowseLibrary": "\u6d4f\u89c8\u5a92\u4f53\u5e93",
- "LabelConfigureMediaBrowser": "\u914d\u7f6eMedia Browser",
+ "LabelConfigureMediaBrowser": "\u914d\u7f6e Media Browser",
"LabelOpenLibraryViewer": "\u6253\u5f00\u5a92\u4f53\u5e93\u6d4f\u89c8\u5668",
"LabelRestartServer": "\u91cd\u542f\u670d\u52a1\u5668",
"LabelShowLogWindow": "\u663e\u793a\u65e5\u5fd7\u7a97\u53e3",
"LabelPrevious": "\u4e0a\u4e00\u4e2a",
- "LabelFinish": "\u7ed3\u675f",
+ "LabelFinish": "\u5b8c\u6210",
"LabelNext": "\u4e0b\u4e00\u4e2a",
"LabelYoureDone": "\u5b8c\u6210\uff01",
- "WelcomeToMediaBrowser": "\u6b22\u8fce\u8fdb\u5165Media Browser\uff01",
+ "WelcomeToMediaBrowser": "\u6b22\u8fce\u4f7f\u7528 Media Browser\uff01",
"TitleMediaBrowser": "Media Browser",
- "ThisWizardWillGuideYou": "\u8be5\u5411\u5bfc\u5c06\u6307\u5bfc\u4f60\u5b8c\u6210\u5b89\u88c5\u8fc7\u7a0b\u3002\u9996\u5148\uff0c\u8bf7\u9009\u62e9\u4f60\u7684\u8bed\u8a00\u3002",
+ "ThisWizardWillGuideYou": "\u8be5\u5411\u5bfc\u5c06\u6307\u5bfc\u4f60\u5b8c\u6210\u5b89\u88c5\u8fc7\u7a0b\u3002\u9996\u5148\uff0c\u8bf7\u9009\u62e9\u4f60\u7684\u9996\u9009\u8bed\u8a00\u3002",
"TellUsAboutYourself": "\u8bf7\u4ecb\u7ecd\u4e00\u4e0b\u4f60\u81ea\u5df1",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "\u4f60\u7684\u540d\u5b57\uff1a",
"MoreUsersCanBeAddedLater": "\u7a0d\u540e\u5728\u63a7\u5236\u53f0\u4e2d\u53ef\u4ee5\u6dfb\u52a0\u66f4\u591a\u7528\u6237\u3002",
- "UserProfilesIntro": "Media Browser\u652f\u6301\u591a\u4e2a\u7528\u6237\u8bbe\u5b9a\uff0c\u80fd\u4f7f\u6bcf\u4e2a\u7528\u6237\u90fd\u62e5\u6709\u81ea\u5df1\u4e13\u5c5e\u7684\u663e\u793a\u8bbe\u7f6e\uff0c\u64ad\u653e\u72b6\u6001\u548c\u5bb6\u957f\u63a7\u5236\u8bbe\u7f6e\u3002",
+ "UserProfilesIntro": "Media Browser \u652f\u6301\u591a\u4e2a\u7528\u6237\u8bbe\u5b9a\uff0c\u80fd\u4f7f\u6bcf\u4e2a\u7528\u6237\u90fd\u62e5\u6709\u81ea\u5df1\u4e13\u5c5e\u7684\u663e\u793a\u8bbe\u7f6e\uff0c\u64ad\u653e\u72b6\u6001\u548c\u5bb6\u957f\u63a7\u5236\u8bbe\u7f6e\u3002",
"LabelWindowsService": "Windows \u670d\u52a1",
"AWindowsServiceHasBeenInstalled": "Windows \u670d\u52a1\u5b89\u88c5\u5b8c\u6210",
- "WindowsServiceIntro1": "Media Browser\u670d\u52a1\u5668\u4f5c\u4e3a\u684c\u9762\u5e94\u7528\u7a0b\u5e8f\u901a\u5e38\u4ee5\u4e00\u4e2a\u6258\u76d8\u56fe\u6807\u8fd0\u884c\uff0c\u4f46\u5982\u679c\u4f60\u559c\u6b22\u5b83\u4f5c\u4e3a\u540e\u53f0\u670d\u52a1\u8fd0\u884c\uff0c\u5b83\u53ef\u4ee5\u4eceWindows\u63a7\u5236\u9762\u677f\u542f\u52a8\u3002",
+ "WindowsServiceIntro1": "Media Browser \u670d\u52a1\u5668\u901a\u5e38\u4ee5\u6258\u76d8\u56fe\u6807\u7684\u5f62\u5f0f\u4ee5\u684c\u9762\u5e94\u7528\u7a0b\u5e8f\u8fd0\u884c\uff0c\u4f46\u5982\u679c\u4f60\u559c\u6b22\u5b83\u4f5c\u4e3a\u540e\u53f0\u670d\u52a1\u8fd0\u884c\uff0c\u5b83\u53ef\u4ee5\u4ece Windows \u63a7\u5236\u9762\u677f\u542f\u52a8\u3002",
"WindowsServiceIntro2": "\u5982\u679c\u4f7f\u7528Windows\u670d\u52a1\uff0c\u8bf7\u6ce8\u610f\uff0c\u5b83\u4e0d\u80fd\u540c\u65f6\u4e3a\u6258\u76d8\u56fe\u6807\u8fd0\u884c\uff0c\u6240\u4ee5\u4f60\u9700\u8981\u9000\u51fa\u6258\u76d8\u4ee5\u8fd0\u884c\u670d\u52a1\u3002\u8be5\u670d\u52a1\u8fd8\u5c06\u9700\u8981\u7ba1\u7406\u5458\u6743\u9650\uff0c\u8be5\u6743\u9650\u53ef\u4ee5\u901a\u8fc7\u63a7\u5236\u9762\u677f\u914d\u7f6e\u3002\u8bf7\u6ce8\u610f\uff0c\u6b64\u65f6\u670d\u52a1\u65e0\u6cd5\u81ea\u52a8\u66f4\u65b0\uff0c\u6240\u4ee5\u65b0\u7684\u7248\u672c\u5c06\u9700\u8981\u624b\u52a8\u66f4\u65b0\u3002",
"WizardCompleted": "\u8fd9\u662f\u73b0\u5728\u6211\u4eec\u6240\u8981\u77e5\u9053\u7684\u3002Media Browser\u5df2\u7ecf\u5f00\u59cb\u6574\u5408\u4f60\u5a92\u4f53\u5e93\u4e2d\u7684\u4fe1\u606f\u3002\u4f60\u53ef\u4ee5\u7ee7\u7eed\u67e5\u770b\u6211\u4eec\u7684\u5176\u4ed6\u5e94\u7528\u7a0b\u5e8f\uff0c\u7136\u540e<b>\u5b8c\u6210<\/b>\u6765\u67e5\u770b <b>\u63a7\u5236\u53f0<\/b>.",
"LabelConfigureSettings": "\u914d\u7f6e\u8bbe\u7f6e",
- "LabelEnableVideoImageExtraction": "\u542f\u7528\u89c6\u9891\u56fe\u7247\u63d0\u53d6",
+ "LabelEnableVideoImageExtraction": "\u542f\u7528\u89c6\u9891\u622a\u56fe\u529f\u80fd",
"VideoImageExtractionHelp": "\u5bf9\u4e8e\u8fd8\u6ca1\u6709\u56fe\u7247\uff0c\u4ee5\u53ca\u6211\u4eec\u65e0\u6cd5\u627e\u5230\u7f51\u7edc\u56fe\u7247\u7684\u89c6\u9891\u3002\u8fd9\u4f1a\u989d\u5916\u589e\u52a0\u4e00\u4e9b\u521d\u59cb\u5316\u5a92\u4f53\u5e93\u7684\u626b\u63cf\u65f6\u95f4\uff0c\u4f46\u4f1a\u4f7f\u5a92\u4f53\u4ecb\u7ecd\u754c\u9762\u66f4\u52a0\u8d4f\u5fc3\u60a6\u76ee\u3002",
- "LabelEnableChapterImageExtractionForMovies": "\u63d0\u53d6\u7535\u5f71\u7ae0\u8282\u56fe\u7247",
+ "LabelEnableChapterImageExtractionForMovies": "\u622a\u53d6\u7535\u5f71\u7ae0\u8282\u56fe\u7247",
"LabelChapterImageExtractionForMoviesHelp": "\u4ece\u7ae0\u8282\u4e2d\u63d0\u53d6\u7684\u56fe\u7247\u5c06\u5141\u8bb8\u5ba2\u6237\u7aef\u663e\u793a\u56fe\u5f62\u9009\u62e9\u83dc\u5355\u3002\u8fd9\u4e2a\u8fc7\u7a0b\u53ef\u80fd\u4f1a\u5f88\u6162\uff0c \u66f4\u591a\u7684\u5360\u7528CPU\u8d44\u6e90\uff0c\u5e76\u4e14\u4f1a\u9700\u8981\u51e0\u4e2aGB\u7684\u786c\u76d8\u7a7a\u95f4\u3002\u5b83\u88ab\u9ed8\u8ba4\u8bbe\u7f6e\u4e8e\u6bcf\u665a\u51cc\u66684\u70b9\u8fd0\u884c\uff0c\u867d\u7136\u8fd9\u53ef\u4ee5\u5728\u8ba1\u5212\u4efb\u52a1\u4e2d\u914d\u7f6e\uff0c\u4f46\u6211\u4eec\u4e0d\u5efa\u8bae\u5728\u9ad8\u5cf0\u4f7f\u7528\u65f6\u95f4\u8fd0\u884c\u8be5\u4efb\u52a1\u3002",
"LabelEnableAutomaticPortMapping": "\u542f\u7528\u81ea\u52a8\u7aef\u53e3\u6620\u5c04",
"LabelEnableAutomaticPortMappingHelp": "UPNP\u5141\u8bb8\u81ea\u52a8\u8def\u7531\u5668\u914d\u7f6e\uff0c\u4ece\u800c\u66f4\u65b9\u4fbf\u7684\u8fdb\u884c\u8fdc\u7a0b\u8bbf\u95ee\u3002\u4f46\u8fd9\u53ef\u80fd\u4e0d\u9002\u7528\u4e8e\u67d0\u4e9b\u578b\u53f7\u7684\u8def\u7531\u5668\u3002",
"ButtonOk": "\u786e\u5b9a",
"ButtonCancel": "\u53d6\u6d88",
"ButtonNew": "\u65b0\u589e",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "\u7535\u5f71",
+ "FolderTypeMusic": "\u97f3\u4e50",
+ "FolderTypeAdultVideos": "\u6210\u4eba\u89c6\u9891",
+ "FolderTypePhotos": "\u56fe\u7247",
+ "FolderTypeMusicVideos": "\u97f3\u4e50\u89c6\u9891",
+ "FolderTypeHomeVideos": "\u5bb6\u5ead\u89c6\u9891",
+ "FolderTypeGames": "\u6e38\u620f",
+ "FolderTypeBooks": "\u4e66\u7c4d",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "\u8bbe\u7f6e\u4f60\u7684\u5a92\u4f53\u5e93",
"ButtonAddMediaFolder": "\u6dfb\u52a0\u5a92\u4f53\u6587\u4ef6\u5939",
"LabelFolderType": "\u6587\u4ef6\u5939\u7c7b\u578b\uff1a",
- "MediaFolderHelpPluginRequired": "* \u9700\u8981\u4f7f\u7528\u4e00\u4e2a\u63d2\u4ef6\uff0c\u4f8b\u5982\uff1aGameBrowser \u6216\u8005 MB Bookshelf\u3002",
"ReferToMediaLibraryWiki": "\u8bf7\u53c2\u9605\u5a92\u4f53\u5e93\u7ef4\u57fa\u3002",
"LabelCountry": "\u56fd\u5bb6\uff1a",
"LabelLanguage": "\u8bed\u8a00\uff1a",
@@ -52,12 +65,16 @@
"TabPreferences": "\u504f\u597d",
"TabPassword": "\u5bc6\u7801",
"TabLibraryAccess": "\u5a92\u4f53\u5e93\u8bbf\u95ee\u6743\u9650",
+ "TabAccess": "Access",
"TabImage": "\u56fe\u7247",
"TabProfile": "\u4e2a\u4eba\u914d\u7f6e",
"TabMetadata": "\u5a92\u4f53\u8d44\u6599",
"TabImages": "\u56fe\u50cf",
"TabNotifications": "\u901a\u77e5",
"TabCollectionTitles": "\u6807\u9898",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "\u663e\u793a\u6bcf\u5b63\u91cc\u7f3a\u5c11\u7684\u5267\u96c6",
"LabelUnairedMissingEpisodesWithinSeasons": "\u663e\u793a\u6bcf\u5b63\u91cc\u672a\u53d1\u5e03\u7684\u5267\u96c6",
"HeaderVideoPlaybackSettings": "\u89c6\u9891\u56de\u653e\u8bbe\u7f6e",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "New Users",
"ButtonSignUp": "Sign up",
"ButtonForgotPassword": "Forgot password?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json b/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json
index 8d87c6897..7257df1e1 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json
@@ -19,6 +19,7 @@
"TitleMediaBrowser": "Media Browser",
"ThisWizardWillGuideYou": "This wizard will help guide you through the setup process. To begin, please select your preferred language.",
"TellUsAboutYourself": "\u8acb\u4ecb\u7d39\u4e00\u4e0b\u81ea\u5df1",
+ "ButtonQuickStartGuide": "Quick start guide",
"LabelYourFirstName": "\u4f60\u7684\u540d\u5b57\uff1a",
"MoreUsersCanBeAddedLater": "\u5f80\u5f8c\u53ef\u4ee5\u5728\u63a7\u5236\u53f0\u5167\u6dfb\u52a0\u66f4\u591a\u7528\u6236\u3002",
"UserProfilesIntro": "Media Browser \u5167\u7f6e\u652f\u6301\u591a\u500b\u7528\u6236\u914d\u7f6e\uff0c\u4f7f\u6bcf\u500b\u7528\u6236\u90fd\u64c1\u6709\u81ea\u5df1\u5c08\u5c6c\u7684\u986f\u793a\u8a2d\u7f6e\uff0c\u64ad\u653e\u72c0\u614b\u548c\u5bb6\u9577\u63a7\u5236\u8a2d\u7f6e\u3002",
@@ -37,10 +38,22 @@
"ButtonOk": "OK",
"ButtonCancel": "\u53d6\u6d88",
"ButtonNew": "\u5275\u5efa",
+ "HeaderSyncJobInfo": "Sync Job",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Movies",
+ "FolderTypeMusic": "Music",
+ "FolderTypeAdultVideos": "Adult videos",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Music videos",
+ "FolderTypeHomeVideos": "Home videos",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Books",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "LabelContentType": "Content type:",
"HeaderSetupLibrary": "\u8a2d\u7f6e\u4f60\u7684\u5a92\u9ad4\u5eab",
"ButtonAddMediaFolder": "\u6dfb\u52a0\u5a92\u9ad4\u6587\u4ef6\u593e",
"LabelFolderType": "\u5a92\u9ad4\u6587\u4ef6\u593e\u985e\u578b\uff1a",
- "MediaFolderHelpPluginRequired": "*\u9700\u8981\u4f7f\u7528\u4e00\u500b\u63d2\u4ef6\uff0c\u4f8b\u5982GameBrowser\u6216MB Bookshelf\u3002",
"ReferToMediaLibraryWiki": "\u53c3\u7167\u5a92\u9ad4\u5eab\u7ef4\u57fa",
"LabelCountry": "\u570b\u5bb6\uff1a",
"LabelLanguage": "\u8a9e\u8a00\uff1a",
@@ -52,12 +65,16 @@
"TabPreferences": "\u504f\u597d",
"TabPassword": "\u5bc6\u78bc",
"TabLibraryAccess": "\u5a92\u9ad4\u5eab\u700f\u89bd\u6b0a\u9650",
+ "TabAccess": "Access",
"TabImage": "\u5716\u50cf",
"TabProfile": "\u914d\u7f6e",
"TabMetadata": "\u5a92\u9ad4\u8cc7\u6599",
"TabImages": "\u5716\u50cf",
"TabNotifications": "Notifications",
"TabCollectionTitles": "\u6a19\u984c",
+ "HeaderDeviceAccess": "Device Access",
+ "OptionEnableAccessFromAllDevices": "Enable access from all devices",
+ "DeviceAccessHelp": "This only applies to devices that can be uniquely identified and will not prevent browser access. Filtering user device access will prevent them from using new devices until they've been approved here.",
"LabelDisplayMissingEpisodesWithinSeasons": "\u986f\u793a\u7bc0\u76ee\u5b63\u5ea6\u5167\u7f3a\u5c11\u7684\u55ae\u5143",
"LabelUnairedMissingEpisodesWithinSeasons": "\u5728\u7bc0\u76ee\u5b63\u5ea6\u5167\u986f\u793a\u9084\u672a\u767c\u4f48\u7684\u55ae\u5143",
"HeaderVideoPlaybackSettings": "\u8996\u983b\u56de\u653e\u8a2d\u7f6e",
@@ -1246,7 +1263,7 @@
"HeaderTrailerReel": "Trailer Reel",
"OptionPlayUnwatchedTrailersOnly": "Play only unwatched trailers",
"HeaderTrailerReelHelp": "Start a trailer reel to play a long running playlist of trailers.",
- "MessageNoTrailersFound": "No trailers found. Install the Trailer channel plugin to import a library of internet trailers.",
+ "MessageNoTrailersFound": "No trailers found. Install the Trailer channel to enhance your movie experience by adding a library of internet trailers.",
"HeaderNewUsers": "New Users",
"ButtonSignUp": "Sign up",
"ButtonForgotPassword": "Forgot password?",
@@ -1279,5 +1296,11 @@
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
- "TitleSync": "Sync"
+ "TitleSync": "Sync",
+ "OptionAllowSyncContent": "Allow syncing media to devices",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",
+ "TabJobs": "Jobs",
+ "TabSyncJobs": "Sync Jobs"
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
index ed014fff7..ccc0737e9 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -51,7 +51,7 @@
</Reference>
<Reference Include="MediaBrowser.Naming, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
- <HintPath>..\packages\MediaBrowser.Naming.1.0.0.18\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
+ <HintPath>..\packages\MediaBrowser.Naming.1.0.0.24\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
</Reference>
<Reference Include="Mono.Nat, Version=1.2.21.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
@@ -64,6 +64,9 @@
<Reference Include="ServiceStack.Api.Swagger">
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.Api.Swagger.dll</HintPath>
</Reference>
+ <Reference Include="SocketHttpListener">
+ <HintPath>..\ThirdParty\SocketHttpListener\SocketHttpListener.dll</HintPath>
+ </Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data.SQLite">
@@ -91,10 +94,6 @@
<Reference Include="ServiceStack.Text">
<HintPath>..\ThirdParty\ServiceStack.Text\ServiceStack.Text.dll</HintPath>
</Reference>
- <Reference Include="websocket-sharp, Version=1.0.2.508, Culture=neutral, PublicKeyToken=5660b08a1845a91e, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\ThirdParty\WebsocketSharp\websocket-sharp.dll</HintPath>
- </Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs">
@@ -183,6 +182,7 @@
<Compile Include="Library\LocalTrailerPostScanTask.cs" />
<Compile Include="Library\MusicManager.cs" />
<Compile Include="Library\PathExtensions.cs" />
+ <Compile Include="Library\Resolvers\SpecialFolderResolver.cs" />
<Compile Include="Library\Resolvers\BaseVideoResolver.cs" />
<Compile Include="Library\Resolvers\PhotoAlbumResolver.cs" />
<Compile Include="Library\Resolvers\PhotoResolver.cs" />
@@ -301,7 +301,6 @@
<Compile Include="Sorting\VideoBitRateComparer.cs" />
<Compile Include="Sync\AppSyncProvider.cs" />
<Compile Include="Sync\CloudSyncProvider.cs" />
- <Compile Include="Sync\MockSyncProvider.cs" />
<Compile Include="Sync\SyncJobProcessor.cs" />
<Compile Include="Sync\SyncManager.cs" />
<Compile Include="Sync\SyncRepository.cs" />
diff --git a/MediaBrowser.Server.Implementations/News/NewsService.cs b/MediaBrowser.Server.Implementations/News/NewsService.cs
index 9eeadfab7..684363d01 100644
--- a/MediaBrowser.Server.Implementations/News/NewsService.cs
+++ b/MediaBrowser.Server.Implementations/News/NewsService.cs
@@ -26,6 +26,14 @@ namespace MediaBrowser.Server.Implementations.News
{
return GetProductNewsInternal(query);
}
+ catch (DirectoryNotFoundException)
+ {
+ // No biggie
+ return new QueryResult<NewsItem>
+ {
+ Items = new NewsItem[] { }
+ };
+ }
catch (FileNotFoundException)
{
// No biggie
diff --git a/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs b/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs
index 3f8b61a45..e74225887 100644
--- a/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs
+++ b/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs
@@ -78,7 +78,7 @@ namespace MediaBrowser.Server.Implementations.Notifications
switch (request.SendToUserMode.Value)
{
case SendToUserType.Admins:
- return _userManager.Users.Where(i => i.Configuration.IsAdministrator)
+ return _userManager.Users.Where(i => i.Policy.IsAdministrator)
.Select(i => i.Id.ToString("N"));
case SendToUserType.All:
return _userManager.Users.Select(i => i.Id.ToString("N"));
diff --git a/MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs b/MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs
index 3c3d7740a..9f75d522c 100644
--- a/MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs
+++ b/MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs
@@ -1,5 +1,5 @@
using MediaBrowser.Common.Events;
-using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization;
diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
index 1f294c325..d9ec9b7e4 100644
--- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs
+++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
@@ -68,12 +68,6 @@ namespace MediaBrowser.Server.Implementations.Session
private readonly IDeviceManager _deviceManager;
/// <summary>
- /// Gets or sets the configuration manager.
- /// </summary>
- /// <value>The configuration manager.</value>
- private readonly IServerConfigurationManager _configurationManager;
-
- /// <summary>
/// The _active connections
/// </summary>
private readonly ConcurrentDictionary<string, SessionInfo> _activeConnections =
@@ -105,18 +99,9 @@ namespace MediaBrowser.Server.Implementations.Session
private readonly SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
- /// <summary>
- /// Initializes a new instance of the <see cref="SessionManager" /> class.
- /// </summary>
- /// <param name="userDataRepository">The user data repository.</param>
- /// <param name="configurationManager">The configuration manager.</param>
- /// <param name="logger">The logger.</param>
- /// <param name="userRepository">The user repository.</param>
- /// <param name="libraryManager">The library manager.</param>
- public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IItemRepository itemRepo, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo, IDeviceManager deviceManager)
+ public SessionManager(IUserDataManager userDataRepository, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IItemRepository itemRepo, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo, IDeviceManager deviceManager)
{
_userDataRepository = userDataRepository;
- _configurationManager = configurationManager;
_logger = logger;
_userRepository = userRepository;
_libraryManager = libraryManager;
@@ -689,7 +674,7 @@ namespace MediaBrowser.Server.Implementations.Session
if (positionTicks.HasValue)
{
- UpdatePlayState(item, data, positionTicks.Value);
+ _userDataRepository.UpdatePlayState(item, data, positionTicks.Value);
await _userDataRepository.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None).ConfigureAwait(false);
}
@@ -779,7 +764,7 @@ namespace MediaBrowser.Server.Implementations.Session
if (positionTicks.HasValue)
{
- playedToCompletion = UpdatePlayState(item, data, positionTicks.Value);
+ playedToCompletion = _userDataRepository.UpdatePlayState(item, data, positionTicks.Value);
}
else
{
@@ -796,65 +781,6 @@ namespace MediaBrowser.Server.Implementations.Session
}
/// <summary>
- /// Updates playstate position for an item but does not save
- /// </summary>
- /// <param name="item">The item</param>
- /// <param name="data">User data for the item</param>
- /// <param name="positionTicks">The current playback position</param>
- private bool UpdatePlayState(BaseItem item, UserItemData data, long positionTicks)
- {
- var playedToCompletion = false;
-
- var hasRuntime = item.RunTimeTicks.HasValue && item.RunTimeTicks > 0;
-
- // If a position has been reported, and if we know the duration
- if (positionTicks > 0 && hasRuntime)
- {
- var pctIn = Decimal.Divide(positionTicks, item.RunTimeTicks.Value) * 100;
-
- // Don't track in very beginning
- if (pctIn < _configurationManager.Configuration.MinResumePct)
- {
- positionTicks = 0;
- }
-
- // If we're at the end, assume completed
- else if (pctIn > _configurationManager.Configuration.MaxResumePct || positionTicks >= item.RunTimeTicks.Value)
- {
- positionTicks = 0;
- data.Played = playedToCompletion = true;
- }
-
- else
- {
- // Enforce MinResumeDuration
- var durationSeconds = TimeSpan.FromTicks(item.RunTimeTicks.Value).TotalSeconds;
-
- if (durationSeconds < _configurationManager.Configuration.MinResumeDurationSeconds)
- {
- positionTicks = 0;
- data.Played = playedToCompletion = true;
- }
- }
- }
- else if (!hasRuntime)
- {
- // If we don't know the runtime we'll just have to assume it was fully played
- data.Played = playedToCompletion = true;
- positionTicks = 0;
- }
-
- if (item is Audio)
- {
- positionTicks = 0;
- }
-
- data.PlaybackPositionTicks = positionTicks;
-
- return playedToCompletion;
- }
-
- /// <summary>
/// Gets the session.
/// </summary>
/// <param name="sessionId">The session identifier.</param>
@@ -1265,6 +1191,14 @@ namespace MediaBrowser.Server.Implementations.Session
var user = _userManager.Users
.FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase));
+ if (user != null && !string.IsNullOrWhiteSpace(request.DeviceId))
+ {
+ if (!_deviceManager.CanAccessDevice(user.Id.ToString("N"), request.DeviceId))
+ {
+ throw new UnauthorizedAccessException("User is not allowed access from this device.");
+ }
+ }
+
var result = await _userManager.AuthenticateUser(request.Username, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint).ConfigureAwait(false);
if (!result)
diff --git a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs
index ed590a1f2..36a7fcbd8 100644
--- a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs
+++ b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs
@@ -1,4 +1,5 @@
using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
diff --git a/MediaBrowser.Server.Implementations/Session/WebSocketController.cs b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs
index 0756aa1ec..7d1057397 100644
--- a/MediaBrowser.Server.Implementations/Session/WebSocketController.cs
+++ b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs
@@ -1,4 +1,4 @@
-using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
@@ -73,17 +73,6 @@ namespace MediaBrowser.Server.Implementations.Session
_logger.ErrorException("Error reporting session ended.", ex);
}
}
- else
- {
- var capabilities = new ClientCapabilities
- {
- PlayableMediaTypes = Session.PlayableMediaTypes,
- SupportedCommands = Session.SupportedCommands,
- SupportsMediaControl = SupportsMediaControl
- };
-
- _sessionManager.ReportCapabilities(Session.Id, capabilities);
- }
}
private IWebSocketConnection GetActiveSocket()
diff --git a/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs b/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs
index 94eed50f6..6cc5be955 100644
--- a/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs
+++ b/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs
@@ -8,7 +8,7 @@ using System.Linq;
namespace MediaBrowser.Server.Implementations.Sync
{
- public class AppSyncProvider : ISyncProvider
+ public class AppSyncProvider : ISyncProvider, IHasUniqueTargetIds
{
private readonly IDeviceManager _deviceManager;
@@ -30,9 +30,25 @@ namespace MediaBrowser.Server.Implementations.Sync
});
}
+ public IEnumerable<SyncTarget> GetSyncTargets(string userId)
+ {
+ return _deviceManager.GetDevices(new DeviceQuery
+ {
+ SupportsSync = true,
+ UserId = userId
+
+ }).Items.Select(i => new SyncTarget
+ {
+ Id = i.Id,
+ Name = i.Name
+ });
+ }
+
public DeviceProfile GetDeviceProfile(SyncTarget target)
{
- return new DeviceProfile();
+ var caps = _deviceManager.GetCapabilities(target.Id);
+
+ return caps == null || caps.DeviceProfile == null ? new DeviceProfile() : caps.DeviceProfile;
}
public string Name
diff --git a/MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs b/MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs
index fd12b1f8a..da3ecdfa6 100644
--- a/MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs
+++ b/MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs
@@ -2,7 +2,6 @@
using MediaBrowser.Controller.Sync;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Sync;
-using System;
using System.Collections.Generic;
using System.Linq;
@@ -10,7 +9,7 @@ namespace MediaBrowser.Server.Implementations.Sync
{
public class CloudSyncProvider : ISyncProvider
{
- private ICloudSyncProvider[] _providers = new ICloudSyncProvider[] {};
+ private ICloudSyncProvider[] _providers = {};
public CloudSyncProvider(IApplicationHost appHost)
{
@@ -22,6 +21,11 @@ namespace MediaBrowser.Server.Implementations.Sync
return new List<SyncTarget>();
}
+ public IEnumerable<SyncTarget> GetSyncTargets(string userId)
+ {
+ return new List<SyncTarget>();
+ }
+
public DeviceProfile GetDeviceProfile(SyncTarget target)
{
return new DeviceProfile();
diff --git a/MediaBrowser.Server.Implementations/Sync/MockSyncProvider.cs b/MediaBrowser.Server.Implementations/Sync/MockSyncProvider.cs
deleted file mode 100644
index 7d29446b9..000000000
--- a/MediaBrowser.Server.Implementations/Sync/MockSyncProvider.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Sync;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Sync;
-using System.Collections.Generic;
-
-namespace MediaBrowser.Server.Implementations.Sync
-{
- public class MockSyncProvider : ISyncProvider
- {
- public string Name
- {
- get { return "Test Sync"; }
- }
-
- public IEnumerable<SyncTarget> GetSyncTargets()
- {
- return new List<SyncTarget>
- {
- new SyncTarget
- {
- Id = GetType().Name.GetMD5().ToString("N"),
- Name = Name
- }
- };
- }
-
- public DeviceProfile GetDeviceProfile(SyncTarget target)
- {
- return new DeviceProfile();
- }
- }
-}
diff --git a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
index 6dda869ee..896e49cb2 100644
--- a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
+++ b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
@@ -1,11 +1,15 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Sync;
+using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Session;
using MediaBrowser.Model.Sync;
using MoreLinq;
@@ -24,19 +28,18 @@ namespace MediaBrowser.Server.Implementations.Sync
private readonly ISyncManager _syncManager;
private readonly ILogger _logger;
private readonly IUserManager _userManager;
+ private readonly ITVSeriesManager _tvSeriesManager;
+ private readonly IMediaEncoder MediaEncoder;
- public SyncJobProcessor(ILibraryManager libraryManager, ISyncRepository syncRepo, ISyncManager syncManager, ILogger logger, IUserManager userManager)
+ public SyncJobProcessor(ILibraryManager libraryManager, ISyncRepository syncRepo, ISyncManager syncManager, ILogger logger, IUserManager userManager, ITVSeriesManager tvSeriesManager, IMediaEncoder mediaEncoder)
{
_libraryManager = libraryManager;
_syncRepo = syncRepo;
_syncManager = syncManager;
_logger = logger;
_userManager = userManager;
- }
-
- public void ProcessJobItem(SyncJob job, SyncJobItem jobItem, SyncTarget target)
- {
-
+ _tvSeriesManager = tvSeriesManager;
+ MediaEncoder = mediaEncoder;
}
public async Task EnsureJobItems(SyncJob job)
@@ -48,7 +51,7 @@ namespace MediaBrowser.Server.Implementations.Sync
throw new InvalidOperationException("Cannot proceed with sync because user no longer exists.");
}
- var items = GetItemsForSync(job.RequestedItemIds, user, job.UnwatchedOnly)
+ var items = (await GetItemsForSync(job.Category, job.ParentId, job.RequestedItemIds, user, job.UnwatchedOnly).ConfigureAwait(false))
.ToList();
var jobItems = _syncRepo.GetJobItems(new SyncJobItemQuery
@@ -62,7 +65,7 @@ namespace MediaBrowser.Server.Implementations.Sync
// Respect ItemLimit, if set
if (job.ItemLimit.HasValue)
{
- if (jobItems.Count >= job.ItemLimit.Value)
+ if (jobItems.Count(j => j.Status != SyncJobItemStatus.RemovedFromDevice && j.Status != SyncJobItemStatus.Failed) >= job.ItemLimit.Value)
{
break;
}
@@ -81,6 +84,7 @@ namespace MediaBrowser.Server.Implementations.Sync
{
Id = Guid.NewGuid().ToString("N"),
ItemId = itemId,
+ ItemName = GetSyncJobItemName(item),
JobId = job.Id,
TargetId = job.TargetId,
DateCreated = DateTime.UtcNow
@@ -98,6 +102,11 @@ namespace MediaBrowser.Server.Implementations.Sync
await UpdateJobStatus(job, jobItems).ConfigureAwait(false);
}
+ private string GetSyncJobItemName(BaseItem item)
+ {
+ return item.Name;
+ }
+
public Task UpdateJobStatus(string id)
{
var job = _syncRepo.GetJob(id);
@@ -128,7 +137,7 @@ namespace MediaBrowser.Server.Implementations.Sync
foreach (var item in jobItems)
{
- if (item.Status == SyncJobItemStatus.Failed || item.Status == SyncJobItemStatus.Completed)
+ if (item.Status == SyncJobItemStatus.Failed || item.Status == SyncJobItemStatus.Synced || item.Status == SyncJobItemStatus.RemovedFromDevice)
{
pct += 100;
}
@@ -171,10 +180,11 @@ namespace MediaBrowser.Server.Implementations.Sync
return _syncRepo.Update(job);
}
- public IEnumerable<BaseItem> GetItemsForSync(IEnumerable<string> itemIds, User user, bool unwatchedOnly)
+ public async Task<IEnumerable<BaseItem>> GetItemsForSync(SyncCategory? category, string parentId, IEnumerable<string> itemIds, User user, bool unwatchedOnly)
{
- var items = itemIds
- .SelectMany(i => GetItemsForSync(i, user))
+ var items = category.HasValue ?
+ await GetItemsForSync(category.Value, parentId, user).ConfigureAwait(false) :
+ itemIds.SelectMany(i => GetItemsForSync(i, user))
.Where(_syncManager.SupportsSync);
if (unwatchedOnly)
@@ -198,6 +208,54 @@ namespace MediaBrowser.Server.Implementations.Sync
return items.DistinctBy(i => i.Id);
}
+ private async Task<IEnumerable<BaseItem>> GetItemsForSync(SyncCategory category, string parentId, User user)
+ {
+ var parent = string.IsNullOrWhiteSpace(parentId)
+ ? user.RootFolder
+ : (Folder)_libraryManager.GetItemById(parentId);
+
+ InternalItemsQuery query;
+
+ switch (category)
+ {
+ case SyncCategory.Latest:
+ query = new InternalItemsQuery
+ {
+ IsFolder = false,
+ SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName },
+ SortOrder = SortOrder.Descending,
+ Recursive = true
+ };
+ break;
+ case SyncCategory.Resume:
+ query = new InternalItemsQuery
+ {
+ IsFolder = false,
+ SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName },
+ SortOrder = SortOrder.Descending,
+ Recursive = true,
+ IsResumable = true,
+ MediaTypes = new[] { MediaType.Video }
+ };
+ break;
+
+ case SyncCategory.NextUp:
+ return _tvSeriesManager.GetNextUp(new NextUpQuery
+ {
+ ParentId = parentId,
+ UserId = user.Id.ToString("N")
+ }).Items;
+
+ default:
+ throw new ArgumentException("Unrecognized category: " + category);
+ }
+
+ query.User = user;
+
+ var result = await parent.GetItems(query).ConfigureAwait(false);
+ return result.Items;
+ }
+
private IEnumerable<BaseItem> GetItemsForSync(string id, User user)
{
var item = _libraryManager.GetItemById(id);
@@ -261,9 +319,10 @@ namespace MediaBrowser.Server.Implementations.Sync
{
await EnsureSyncJobs(cancellationToken).ConfigureAwait(false);
+ // If it already has a converting status then is must have been aborted during conversion
var result = _syncRepo.GetJobItems(new SyncJobItemQuery
{
- IsCompleted = false
+ Statuses = new List<SyncJobItemStatus> { SyncJobItemStatus.Queued, SyncJobItemStatus.Converting }
});
var jobItems = result.Items;
@@ -278,10 +337,7 @@ namespace MediaBrowser.Server.Implementations.Sync
cancellationToken.ThrowIfCancellationRequested();
- if (item.Status == SyncJobItemStatus.Queued)
- {
- await ProcessJobItem(item, cancellationToken).ConfigureAwait(false);
- }
+ await ProcessJobItem(item, cancellationToken).ConfigureAwait(false);
var job = _syncRepo.GetJob(item.JobId);
await UpdateJobStatus(job).ConfigureAwait(false);
@@ -316,39 +372,30 @@ namespace MediaBrowser.Server.Implementations.Sync
var video = item as Video;
if (video != null)
{
- jobItem.OutputPath = await Sync(jobItem, video, deviceProfile, cancellationToken).ConfigureAwait(false);
+ await Sync(jobItem, video, deviceProfile, cancellationToken).ConfigureAwait(false);
}
else if (item is Audio)
{
- jobItem.OutputPath = await Sync(jobItem, (Audio)item, deviceProfile, cancellationToken).ConfigureAwait(false);
+ await Sync(jobItem, (Audio)item, deviceProfile, cancellationToken).ConfigureAwait(false);
}
else if (item is Photo)
{
- jobItem.OutputPath = await Sync(jobItem, (Photo)item, deviceProfile, cancellationToken).ConfigureAwait(false);
- }
-
- else if (item is Game)
- {
- jobItem.OutputPath = await Sync(jobItem, (Game)item, deviceProfile, cancellationToken).ConfigureAwait(false);
+ await Sync(jobItem, (Photo)item, deviceProfile, cancellationToken).ConfigureAwait(false);
}
- else if (item is Book)
+ else
{
- jobItem.OutputPath = await Sync(jobItem, (Book)item, deviceProfile, cancellationToken).ConfigureAwait(false);
+ await SyncGeneric(jobItem, item, deviceProfile, cancellationToken).ConfigureAwait(false);
}
-
- jobItem.Progress = 50;
- jobItem.Status = SyncJobItemStatus.Transferring;
- await _syncRepo.Update(jobItem).ConfigureAwait(false);
}
- private async Task<string> Sync(SyncJobItem jobItem, Video item, DeviceProfile profile, CancellationToken cancellationToken)
+ private async Task Sync(SyncJobItem jobItem, Video item, DeviceProfile profile, CancellationToken cancellationToken)
{
var options = new VideoOptions
{
- Context = EncodingContext.Streaming,
+ Context = EncodingContext.Static,
ItemId = item.Id.ToString("N"),
DeviceId = jobItem.TargetId,
Profile = profile,
@@ -358,28 +405,41 @@ namespace MediaBrowser.Server.Implementations.Sync
var streamInfo = new StreamBuilder().BuildVideoItem(options);
var mediaSource = streamInfo.MediaSource;
- if (streamInfo.PlayMethod != PlayMethod.Transcode)
+ jobItem.MediaSourceId = streamInfo.MediaSourceId;
+
+ if (streamInfo.PlayMethod == PlayMethod.Transcode)
+ {
+ jobItem.Status = SyncJobItemStatus.Converting;
+ await _syncRepo.Update(jobItem).ConfigureAwait(false);
+
+ jobItem.OutputPath = await MediaEncoder.EncodeVideo(new EncodingJobOptions(streamInfo, profile), new Progress<double>(), cancellationToken);
+ }
+ else
{
if (mediaSource.Protocol == MediaProtocol.File)
{
- return mediaSource.Path;
+ jobItem.OutputPath = mediaSource.Path;
}
- if (mediaSource.Protocol == MediaProtocol.Http)
+ else if (mediaSource.Protocol == MediaProtocol.Http)
{
- return await DownloadFile(jobItem, mediaSource, cancellationToken).ConfigureAwait(false);
+ jobItem.OutputPath = await DownloadFile(jobItem, mediaSource, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ throw new InvalidOperationException(string.Format("Cannot direct stream {0} protocol", mediaSource.Protocol));
}
- throw new InvalidOperationException(string.Format("Cannot direct stream {0} protocol", mediaSource.Protocol));
}
- // TODO: Transcode
- return mediaSource.Path;
+ jobItem.Progress = 50;
+ jobItem.Status = SyncJobItemStatus.Transferring;
+ await _syncRepo.Update(jobItem).ConfigureAwait(false);
}
- private async Task<string> Sync(SyncJobItem jobItem, Audio item, DeviceProfile profile, CancellationToken cancellationToken)
+ private async Task Sync(SyncJobItem jobItem, Audio item, DeviceProfile profile, CancellationToken cancellationToken)
{
var options = new AudioOptions
{
- Context = EncodingContext.Streaming,
+ Context = EncodingContext.Static,
ItemId = item.Id.ToString("N"),
DeviceId = jobItem.TargetId,
Profile = profile,
@@ -389,36 +449,52 @@ namespace MediaBrowser.Server.Implementations.Sync
var streamInfo = new StreamBuilder().BuildAudioItem(options);
var mediaSource = streamInfo.MediaSource;
- if (streamInfo.PlayMethod != PlayMethod.Transcode)
+ jobItem.MediaSourceId = streamInfo.MediaSourceId;
+
+ if (streamInfo.PlayMethod == PlayMethod.Transcode)
+ {
+ jobItem.Status = SyncJobItemStatus.Converting;
+ await _syncRepo.Update(jobItem).ConfigureAwait(false);
+
+ jobItem.OutputPath = await MediaEncoder.EncodeAudio(new EncodingJobOptions(streamInfo, profile), new Progress<double>(), cancellationToken);
+ }
+ else
{
if (mediaSource.Protocol == MediaProtocol.File)
{
- return mediaSource.Path;
+ jobItem.OutputPath = mediaSource.Path;
}
- if (mediaSource.Protocol == MediaProtocol.Http)
+ else if (mediaSource.Protocol == MediaProtocol.Http)
{
- return await DownloadFile(jobItem, mediaSource, cancellationToken).ConfigureAwait(false);
+ jobItem.OutputPath = await DownloadFile(jobItem, mediaSource, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ throw new InvalidOperationException(string.Format("Cannot direct stream {0} protocol", mediaSource.Protocol));
}
- throw new InvalidOperationException(string.Format("Cannot direct stream {0} protocol", mediaSource.Protocol));
}
- // TODO: Transcode
- return mediaSource.Path;
+ jobItem.Progress = 50;
+ jobItem.Status = SyncJobItemStatus.Transferring;
+ await _syncRepo.Update(jobItem).ConfigureAwait(false);
}
- private async Task<string> Sync(SyncJobItem jobItem, Photo item, DeviceProfile profile, CancellationToken cancellationToken)
+ private async Task Sync(SyncJobItem jobItem, Photo item, DeviceProfile profile, CancellationToken cancellationToken)
{
- return item.Path;
- }
+ jobItem.OutputPath = item.Path;
- private async Task<string> Sync(SyncJobItem jobItem, Game item, DeviceProfile profile, CancellationToken cancellationToken)
- {
- return item.Path;
+ jobItem.Progress = 50;
+ jobItem.Status = SyncJobItemStatus.Transferring;
+ await _syncRepo.Update(jobItem).ConfigureAwait(false);
}
- private async Task<string> Sync(SyncJobItem jobItem, Book item, DeviceProfile profile, CancellationToken cancellationToken)
+ private async Task SyncGeneric(SyncJobItem jobItem, BaseItem item, DeviceProfile profile, CancellationToken cancellationToken)
{
- return item.Path;
+ jobItem.OutputPath = item.Path;
+
+ jobItem.Progress = 50;
+ jobItem.Status = SyncJobItemStatus.Transferring;
+ await _syncRepo.Update(jobItem).ConfigureAwait(false);
}
private async Task<string> DownloadFile(SyncJobItem jobItem, MediaSourceInfo mediaSource, CancellationToken cancellationToken)
diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs
index 664ec4038..3b2d70f84 100644
--- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs
+++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs
@@ -1,18 +1,27 @@
-using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Sync;
+using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Sync;
+using MediaBrowser.Model.Users;
using MoreLinq;
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Threading.Tasks;
@@ -25,16 +34,24 @@ namespace MediaBrowser.Server.Implementations.Sync
private readonly IImageProcessor _imageProcessor;
private readonly ILogger _logger;
private readonly IUserManager _userManager;
+ private readonly Func<IDtoService> _dtoService;
+ private readonly IApplicationHost _appHost;
+ private readonly ITVSeriesManager _tvSeriesManager;
+ private readonly Func<IMediaEncoder> _mediaEncoder;
private ISyncProvider[] _providers = { };
- public SyncManager(ILibraryManager libraryManager, ISyncRepository repo, IImageProcessor imageProcessor, ILogger logger, IUserManager userManager)
+ public SyncManager(ILibraryManager libraryManager, ISyncRepository repo, IImageProcessor imageProcessor, ILogger logger, IUserManager userManager, Func<IDtoService> dtoService, IApplicationHost appHost, ITVSeriesManager tvSeriesManager, Func<IMediaEncoder> mediaEncoder)
{
_libraryManager = libraryManager;
_repo = repo;
_imageProcessor = imageProcessor;
_logger = logger;
_userManager = userManager;
+ _dtoService = dtoService;
+ _appHost = appHost;
+ _tvSeriesManager = tvSeriesManager;
+ _mediaEncoder = mediaEncoder;
}
public void AddParts(IEnumerable<ISyncProvider> providers)
@@ -44,12 +61,12 @@ namespace MediaBrowser.Server.Implementations.Sync
public async Task<SyncJobCreationResult> CreateJob(SyncJobRequest request)
{
- var processor = new SyncJobProcessor(_libraryManager, _repo, this, _logger, _userManager);
+ var processor = new SyncJobProcessor(_libraryManager, _repo, this, _logger, _userManager, _tvSeriesManager, _mediaEncoder());
var user = _userManager.GetUserById(request.UserId);
- var items = processor
- .GetItemsForSync(request.ItemIds, user, request.UnwatchedOnly)
+ var items = (await processor
+ .GetItemsForSync(request.Category, request.ParentId, request.ItemIds, user, request.UnwatchedOnly).ConfigureAwait(false))
.ToList();
if (items.Any(i => !SupportsSync(i)))
@@ -57,9 +74,12 @@ namespace MediaBrowser.Server.Implementations.Sync
throw new ArgumentException("Item does not support sync.");
}
- if (string.IsNullOrWhiteSpace(request.Name) && request.ItemIds.Count == 1)
+ if (string.IsNullOrWhiteSpace(request.Name))
{
- request.Name = GetDefaultName(_libraryManager.GetItemById(request.ItemIds[0]));
+ if (request.ItemIds.Count == 1)
+ {
+ request.Name = GetDefaultName(_libraryManager.GetItemById(request.ItemIds[0]));
+ }
}
if (string.IsNullOrWhiteSpace(request.Name))
@@ -68,7 +88,12 @@ namespace MediaBrowser.Server.Implementations.Sync
}
var target = GetSyncTargets(request.UserId)
- .First(i => string.Equals(request.TargetId, i.Id));
+ .FirstOrDefault(i => string.Equals(request.TargetId, i.Id));
+
+ if (target == null)
+ {
+ throw new ArgumentException("Sync target not found.");
+ }
var jobId = Guid.NewGuid().ToString("N");
@@ -80,7 +105,7 @@ namespace MediaBrowser.Server.Implementations.Sync
UserId = request.UserId,
UnwatchedOnly = request.UnwatchedOnly,
ItemLimit = request.ItemLimit,
- RequestedItemIds = request.ItemIds,
+ RequestedItemIds = request.ItemIds ?? new List<string> { },
DateCreated = DateTime.UtcNow,
DateLastModified = DateTime.UtcNow,
SyncNewContent = request.SyncNewContent,
@@ -106,21 +131,49 @@ namespace MediaBrowser.Server.Implementations.Sync
};
}
- public QueryResult<SyncJob> GetJobs(SyncJobQuery query)
+ public Task UpdateJob(SyncJob job)
+ {
+ // Get fresh from the db and only update the fields that are supported to be changed.
+ var instance = _repo.GetJob(job.Id);
+
+ instance.Name = job.Name;
+ instance.Quality = job.Quality;
+ instance.UnwatchedOnly = job.UnwatchedOnly;
+ instance.SyncNewContent = job.SyncNewContent;
+ instance.ItemLimit = job.ItemLimit;
+
+ return _repo.Update(instance);
+ }
+
+ public async Task<QueryResult<SyncJob>> GetJobs(SyncJobQuery query)
{
var result = _repo.GetJobs(query);
- result.Items.ForEach(FillMetadata);
+ foreach (var item in result.Items)
+ {
+ await FillMetadata(item).ConfigureAwait(false);
+ }
return result;
}
- private void FillMetadata(SyncJob job)
+ private async Task FillMetadata(SyncJob job)
{
var item = job.RequestedItemIds
.Select(_libraryManager.GetItemById)
.FirstOrDefault(i => i != null);
+ if (item == null)
+ {
+ var processor = new SyncJobProcessor(_libraryManager, _repo, this, _logger, _userManager, _tvSeriesManager, _mediaEncoder());
+
+ var user = _userManager.GetUserById(job.UserId);
+
+ item = (await processor
+ .GetItemsForSync(job.Category, job.ParentId, job.RequestedItemIds, user, job.UnwatchedOnly).ConfigureAwait(false))
+ .FirstOrDefault();
+ }
+
if (item != null)
{
var hasSeries = item as IHasSeries;
@@ -136,13 +189,25 @@ namespace MediaBrowser.Server.Implementations.Sync
}
var primaryImage = item.GetImageInfo(ImageType.Primary, 0);
+ var itemWithImage = item;
+
+ if (primaryImage == null)
+ {
+ var parentWithImage = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Primary));
+
+ if (parentWithImage != null)
+ {
+ itemWithImage = parentWithImage;
+ primaryImage = parentWithImage.GetImageInfo(ImageType.Primary, 0);
+ }
+ }
if (primaryImage != null)
{
try
{
- job.PrimaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary);
- job.PrimaryImageItemId = item.Id.ToString("N");
+ job.PrimaryImageTag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Primary);
+ job.PrimaryImageItemId = itemWithImage.Id.ToString("N");
}
catch (Exception ex)
@@ -153,6 +218,44 @@ namespace MediaBrowser.Server.Implementations.Sync
}
}
+ private void FillMetadata(SyncJobItem jobItem)
+ {
+ var item = _libraryManager.GetItemById(jobItem.ItemId);
+
+ if (item == null)
+ {
+ return;
+ }
+
+ var primaryImage = item.GetImageInfo(ImageType.Primary, 0);
+ var itemWithImage = item;
+
+ if (primaryImage == null)
+ {
+ var parentWithImage = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Primary));
+
+ if (parentWithImage != null)
+ {
+ itemWithImage = parentWithImage;
+ primaryImage = parentWithImage.GetImageInfo(ImageType.Primary, 0);
+ }
+ }
+
+ if (primaryImage != null)
+ {
+ try
+ {
+ jobItem.PrimaryImageTag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Primary);
+ jobItem.PrimaryImageItemId = itemWithImage.Id.ToString("N");
+
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error getting image info", ex);
+ }
+ }
+ }
+
public Task CancelJob(string id)
{
return _repo.DeleteJob(id);
@@ -172,17 +275,23 @@ namespace MediaBrowser.Server.Implementations.Sync
private IEnumerable<SyncTarget> GetSyncTargets(ISyncProvider provider, string userId)
{
- var providerId = GetSyncProviderId(provider);
-
- return provider.GetSyncTargets().Select(i => new SyncTarget
+ return provider.GetSyncTargets(userId).Select(i => new SyncTarget
{
Name = i.Name,
- Id = GetSyncTargetId(providerId, i)
+ Id = GetSyncTargetId(provider, i)
});
}
- private string GetSyncTargetId(string providerId, SyncTarget target)
+ private string GetSyncTargetId(ISyncProvider provider, SyncTarget target)
{
+ var hasUniqueId = provider as IHasUniqueTargetIds;
+
+ if (hasUniqueId != null)
+ {
+ return target.Id;
+ }
+
+ var providerId = GetSyncProviderId(provider);
return (providerId + "-" + target.Id).GetMD5().ToString("N");
}
@@ -239,10 +348,21 @@ namespace MediaBrowser.Server.Implementations.Sync
}
}
+ if (item is LiveTvChannel || item is IChannelItem || item is ILiveTvRecording)
+ {
+ return false;
+ }
+
+ // It would be nice to support these later
+ if (item is Game || item is Book)
+ {
+ return false;
+ }
+
return true;
}
- return item.LocationType == LocationType.FileSystem || item is Season;
+ return item.LocationType == LocationType.FileSystem || item is Season || item is ILiveTvRecording;
}
private string GetDefaultName(BaseItem item)
@@ -270,12 +390,12 @@ namespace MediaBrowser.Server.Implementations.Sync
{
var jobItem = _repo.GetJobItem(id);
- jobItem.Status = SyncJobItemStatus.Completed;
+ jobItem.Status = SyncJobItemStatus.Synced;
jobItem.Progress = 100;
await _repo.Update(jobItem).ConfigureAwait(false);
- var processor = new SyncJobProcessor(_libraryManager, _repo, this, _logger, _userManager);
+ var processor = new SyncJobProcessor(_libraryManager, _repo, this, _logger, _userManager, _tvSeriesManager, _mediaEncoder());
await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false);
}
@@ -287,7 +407,162 @@ namespace MediaBrowser.Server.Implementations.Sync
public QueryResult<SyncJobItem> GetJobItems(SyncJobItemQuery query)
{
- return _repo.GetJobItems(query);
+ var result = _repo.GetJobItems(query);
+
+ if (query.AddMetadata)
+ {
+ result.Items.ForEach(FillMetadata);
+ }
+
+ return result;
+ }
+
+ private SyncedItem GetJobItemInfo(SyncJobItem jobItem)
+ {
+ var job = _repo.GetJob(jobItem.JobId);
+
+ var libraryItem = _libraryManager.GetItemById(jobItem.ItemId);
+
+ var syncedItem = new SyncedItem
+ {
+ SyncJobId = jobItem.JobId,
+ SyncJobItemId = jobItem.Id,
+ ServerId = _appHost.SystemId,
+ UserId = job.UserId
+ };
+
+ var dtoOptions = new DtoOptions();
+
+ // Remove some bloat
+ dtoOptions.Fields.Remove(ItemFields.MediaStreams);
+ dtoOptions.Fields.Remove(ItemFields.IndexOptions);
+ dtoOptions.Fields.Remove(ItemFields.MediaSourceCount);
+ dtoOptions.Fields.Remove(ItemFields.OriginalPrimaryImageAspectRatio);
+ dtoOptions.Fields.Remove(ItemFields.Path);
+ dtoOptions.Fields.Remove(ItemFields.SeriesGenres);
+ dtoOptions.Fields.Remove(ItemFields.Settings);
+ dtoOptions.Fields.Remove(ItemFields.SyncInfo);
+
+ syncedItem.Item = _dtoService().GetBaseItemDto(libraryItem, dtoOptions);
+
+ // TODO: this should be the media source of the transcoded output
+ syncedItem.Item.MediaSources = syncedItem.Item.MediaSources
+ .Where(i => string.Equals(i.Id, jobItem.MediaSourceId))
+ .ToList();
+
+ var mediaSource = syncedItem.Item.MediaSources
+ .FirstOrDefault(i => string.Equals(i.Id, jobItem.MediaSourceId));
+
+ // This will be null for items that are not audio/video
+ if (mediaSource == null)
+ {
+ syncedItem.OriginalFileName = Path.GetFileName(libraryItem.Path);
+ }
+ else
+ {
+ syncedItem.OriginalFileName = Path.GetFileName(mediaSource.Path);
+ }
+
+ return syncedItem;
+ }
+
+ public Task ReportOfflineAction(UserAction action)
+ {
+ return Task.FromResult(true);
+ }
+
+ public List<SyncedItem> GetReadySyncItems(string targetId)
+ {
+ var jobItemResult = GetJobItems(new SyncJobItemQuery
+ {
+ TargetId = targetId,
+ Statuses = new List<SyncJobItemStatus> { SyncJobItemStatus.Transferring }
+ });
+
+ return jobItemResult.Items.Select(GetJobItemInfo)
+ .ToList();
+ }
+
+ public async Task<SyncDataResponse> SyncData(SyncDataRequest request)
+ {
+ var jobItemResult = GetJobItems(new SyncJobItemQuery
+ {
+ TargetId = request.TargetId,
+ Statuses = new List<SyncJobItemStatus> { SyncJobItemStatus.Synced }
+ });
+
+ var response = new SyncDataResponse();
+
+ foreach (var jobItem in jobItemResult.Items)
+ {
+ if (request.LocalItemIds.Contains(jobItem.ItemId, StringComparer.OrdinalIgnoreCase))
+ {
+ var job = _repo.GetJob(jobItem.JobId);
+ var user = _userManager.GetUserById(job.UserId);
+
+ if (user == null)
+ {
+ // Tell the device to remove it since the user is gone now
+ response.ItemIdsToRemove.Add(jobItem.ItemId);
+ }
+ else if (job.UnwatchedOnly)
+ {
+ var libraryItem = _libraryManager.GetItemById(jobItem.ItemId);
+
+ if (IsLibraryItemAvailable(libraryItem))
+ {
+ if (libraryItem.IsPlayed(user) && libraryItem is Video)
+ {
+ // Tell the device to remove it since it has been played
+ response.ItemIdsToRemove.Add(jobItem.ItemId);
+ }
+ }
+ else
+ {
+ // Tell the device to remove it since it's no longer available
+ response.ItemIdsToRemove.Add(jobItem.ItemId);
+ }
+ }
+ }
+ else
+ {
+ // Content is no longer on the device
+ jobItem.Status = SyncJobItemStatus.RemovedFromDevice;
+ await _repo.Update(jobItem).ConfigureAwait(false);
+ }
+ }
+
+ // Now check each item that's on the device
+ foreach (var itemId in request.LocalItemIds)
+ {
+ // See if it's already marked for removal
+ if (response.ItemIdsToRemove.Contains(itemId, StringComparer.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ // If there isn't a sync job for this item, mark it for removal
+ if (!jobItemResult.Items.Any(i => string.Equals(itemId, i.ItemId, StringComparison.OrdinalIgnoreCase)))
+ {
+ response.ItemIdsToRemove.Add(itemId);
+ }
+ }
+
+ response.ItemIdsToRemove = response.ItemIdsToRemove.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
+
+ return response;
+ }
+
+ private bool IsLibraryItemAvailable(BaseItem item)
+ {
+ if (item == null)
+ {
+ return false;
+ }
+
+ // TODO: Make sure it hasn't been deleted
+
+ return true;
}
}
}
diff --git a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs
index 7825b9e9a..09c1b316e 100644
--- a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs
+++ b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs
@@ -24,6 +24,7 @@ namespace MediaBrowser.Server.Implementations.Sync
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private IDbCommand _deleteJobCommand;
+ private IDbCommand _deleteJobItemsCommand;
private IDbCommand _saveJobCommand;
private IDbCommand _saveJobItemCommand;
@@ -35,7 +36,7 @@ namespace MediaBrowser.Server.Implementations.Sync
public async Task Initialize()
{
- var dbFile = Path.Combine(_appPaths.DataPath, "sync6.db");
+ var dbFile = Path.Combine(_appPaths.DataPath, "sync10.db");
_connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false);
@@ -44,7 +45,7 @@ namespace MediaBrowser.Server.Implementations.Sync
"create table if not exists SyncJobs (Id GUID PRIMARY KEY, TargetId TEXT NOT NULL, Name TEXT NOT NULL, Quality TEXT NOT NULL, Status TEXT NOT NULL, Progress FLOAT, UserId TEXT NOT NULL, ItemIds TEXT NOT NULL, Category TEXT, ParentId TEXT, UnwatchedOnly BIT, ItemLimit INT, SyncNewContent BIT, DateCreated DateTime, DateLastModified DateTime, ItemCount int)",
"create index if not exists idx_SyncJobs on SyncJobs(Id)",
- "create table if not exists SyncJobItems (Id GUID PRIMARY KEY, ItemId TEXT, JobId TEXT, OutputPath TEXT, Status TEXT, TargetId TEXT, DateCreated DateTime, Progress FLOAT)",
+ "create table if not exists SyncJobItems (Id GUID PRIMARY KEY, ItemId TEXT, ItemName TEXT, MediaSourceId TEXT, JobId TEXT, OutputPath TEXT, Status TEXT, TargetId TEXT, DateCreated DateTime, Progress FLOAT)",
"create index if not exists idx_SyncJobItems on SyncJobs(Id)",
//pragmas
@@ -61,9 +62,13 @@ namespace MediaBrowser.Server.Implementations.Sync
private void PrepareStatements()
{
_deleteJobCommand = _connection.CreateCommand();
- _deleteJobCommand.CommandText = "delete from SyncJobs where Id=@Id; delete from SyncJobItems where JobId=@Id";
+ _deleteJobCommand.CommandText = "delete from SyncJobs where Id=@Id";
_deleteJobCommand.Parameters.Add(_deleteJobCommand, "@Id");
+ _deleteJobItemsCommand = _connection.CreateCommand();
+ _deleteJobItemsCommand.CommandText = "delete from SyncJobItems where JobId=@JobId";
+ _deleteJobItemsCommand.Parameters.Add(_deleteJobItemsCommand, "@JobId");
+
_saveJobCommand = _connection.CreateCommand();
_saveJobCommand.CommandText = "replace into SyncJobs (Id, TargetId, Name, Quality, Status, Progress, UserId, ItemIds, Category, ParentId, UnwatchedOnly, ItemLimit, SyncNewContent, DateCreated, DateLastModified, ItemCount) values (@Id, @TargetId, @Name, @Quality, @Status, @Progress, @UserId, @ItemIds, @Category, @ParentId, @UnwatchedOnly, @ItemLimit, @SyncNewContent, @DateCreated, @DateLastModified, @ItemCount)";
@@ -85,20 +90,22 @@ namespace MediaBrowser.Server.Implementations.Sync
_saveJobCommand.Parameters.Add(_saveJobCommand, "@ItemCount");
_saveJobItemCommand = _connection.CreateCommand();
- _saveJobItemCommand.CommandText = "replace into SyncJobItems (Id, ItemId, JobId, OutputPath, Status, TargetId, DateCreated, Progress) values (@Id, @ItemId, @JobId, @OutputPath, @Status, @TargetId, @DateCreated, @Progress)";
-
- _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@Id");
- _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@ItemId");
- _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@JobId");
- _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@OutputPath");
- _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@Status");
- _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@TargetId");
- _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@DateCreated");
- _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@Progress");
+ _saveJobItemCommand.CommandText = "replace into SyncJobItems (Id, ItemId, ItemName, MediaSourceId, JobId, OutputPath, Status, TargetId, DateCreated, Progress) values (@Id, @ItemId, @ItemName, @MediaSourceId, @JobId, @OutputPath, @Status, @TargetId, @DateCreated, @Progress)";
+
+ _saveJobItemCommand.Parameters.Add(_saveJobItemCommand, "@Id");
+ _saveJobItemCommand.Parameters.Add(_saveJobItemCommand, "@ItemId");
+ _saveJobItemCommand.Parameters.Add(_saveJobItemCommand, "@ItemName");
+ _saveJobItemCommand.Parameters.Add(_saveJobItemCommand, "@MediaSourceId");
+ _saveJobItemCommand.Parameters.Add(_saveJobItemCommand, "@JobId");
+ _saveJobItemCommand.Parameters.Add(_saveJobItemCommand, "@OutputPath");
+ _saveJobItemCommand.Parameters.Add(_saveJobItemCommand, "@Status");
+ _saveJobItemCommand.Parameters.Add(_saveJobItemCommand, "@TargetId");
+ _saveJobItemCommand.Parameters.Add(_saveJobItemCommand, "@DateCreated");
+ _saveJobItemCommand.Parameters.Add(_saveJobItemCommand, "@Progress");
}
private const string BaseJobSelectText = "select Id, TargetId, Name, Quality, Status, Progress, UserId, ItemIds, Category, ParentId, UnwatchedOnly, ItemLimit, SyncNewContent, DateCreated, DateLastModified, ItemCount from SyncJobs";
- private const string BaseJobItemSelectText = "select Id, ItemId, JobId, OutputPath, Status, TargetId, DateCreated, Progress from SyncJobItems";
+ private const string BaseJobItemSelectText = "select Id, ItemId, ItemName, MediaSourceId, JobId, OutputPath, Status, TargetId, DateCreated, Progress from SyncJobItems";
public SyncJob GetJob(string id)
{
@@ -113,7 +120,7 @@ namespace MediaBrowser.Server.Implementations.Sync
{
throw new ArgumentNullException("id");
}
-
+
using (var cmd = _connection.CreateCommand())
{
cmd.CommandText = BaseJobSelectText + " where Id=@Id";
@@ -163,7 +170,7 @@ namespace MediaBrowser.Server.Implementations.Sync
if (!reader.IsDBNull(7))
{
- info.RequestedItemIds = reader.GetString(7).Split(',').ToList();
+ info.RequestedItemIds = reader.GetString(7).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
}
if (!reader.IsDBNull(8))
@@ -221,7 +228,7 @@ namespace MediaBrowser.Server.Implementations.Sync
_saveJobCommand.GetParameter(index++).Value = job.TargetId;
_saveJobCommand.GetParameter(index++).Value = job.Name;
_saveJobCommand.GetParameter(index++).Value = job.Quality;
- _saveJobCommand.GetParameter(index++).Value = job.Status;
+ _saveJobCommand.GetParameter(index++).Value = job.Status.ToString();
_saveJobCommand.GetParameter(index++).Value = job.Progress;
_saveJobCommand.GetParameter(index++).Value = job.UserId;
_saveJobCommand.GetParameter(index++).Value = string.Join(",", job.RequestedItemIds.ToArray());
@@ -289,11 +296,14 @@ namespace MediaBrowser.Server.Implementations.Sync
var index = 0;
_deleteJobCommand.GetParameter(index++).Value = new Guid(id);
-
_deleteJobCommand.Transaction = transaction;
-
_deleteJobCommand.ExecuteNonQuery();
+ index = 0;
+ _deleteJobItemsCommand.GetParameter(index++).Value = id;
+ _deleteJobItemsCommand.Transaction = transaction;
+ _deleteJobItemsCommand.ExecuteNonQuery();
+
transaction.Commit();
}
catch (OperationCanceledException)
@@ -357,6 +367,11 @@ namespace MediaBrowser.Server.Implementations.Sync
whereClauses.Add("TargetId=@TargetId");
cmd.Parameters.Add(cmd, "@TargetId", DbType.String).Value = query.TargetId;
}
+ if (!string.IsNullOrWhiteSpace(query.UserId))
+ {
+ whereClauses.Add("UserId=@UserId");
+ cmd.Parameters.Add(cmd, "@UserId", DbType.String).Value = query.UserId;
+ }
var whereTextWithoutPaging = whereClauses.Count == 0 ?
string.Empty :
@@ -457,23 +472,12 @@ namespace MediaBrowser.Server.Implementations.Sync
whereClauses.Add("TargetId=@TargetId");
cmd.Parameters.Add(cmd, "@TargetId", DbType.String).Value = query.TargetId;
}
- if (query.Status.HasValue)
- {
- whereClauses.Add("Status=@Status");
- cmd.Parameters.Add(cmd, "@Status", DbType.String).Value = query.Status.Value.ToString();
- }
- if (query.IsCompleted.HasValue)
+ if (query.Statuses.Count > 0)
{
- if (query.IsCompleted.Value)
- {
- whereClauses.Add("Status=@Status");
- }
- else
- {
- whereClauses.Add("Status<>@Status");
- }
- cmd.Parameters.Add(cmd, "@Status", DbType.String).Value = SyncJobStatus.Completed.ToString();
+ var statuses = string.Join(",", query.Statuses.Select(i => "'" + i.ToString() + "'").ToArray());
+
+ whereClauses.Add(string.Format("Status in ({0})", statuses));
}
var whereTextWithoutPaging = whereClauses.Count == 0 ?
@@ -549,9 +553,11 @@ namespace MediaBrowser.Server.Implementations.Sync
_saveJobItemCommand.GetParameter(index++).Value = new Guid(jobItem.Id);
_saveJobItemCommand.GetParameter(index++).Value = jobItem.ItemId;
+ _saveJobItemCommand.GetParameter(index++).Value = jobItem.ItemName;
+ _saveJobItemCommand.GetParameter(index++).Value = jobItem.MediaSourceId;
_saveJobItemCommand.GetParameter(index++).Value = jobItem.JobId;
_saveJobItemCommand.GetParameter(index++).Value = jobItem.OutputPath;
- _saveJobItemCommand.GetParameter(index++).Value = jobItem.Status;
+ _saveJobItemCommand.GetParameter(index++).Value = jobItem.Status.ToString();
_saveJobItemCommand.GetParameter(index++).Value = jobItem.TargetId;
_saveJobItemCommand.GetParameter(index++).Value = jobItem.DateCreated;
_saveJobItemCommand.GetParameter(index++).Value = jobItem.Progress;
@@ -598,29 +604,40 @@ namespace MediaBrowser.Server.Implementations.Sync
var info = new SyncJobItem
{
Id = reader.GetGuid(0).ToString("N"),
- ItemId = reader.GetString(1),
- JobId = reader.GetString(2)
+ ItemId = reader.GetString(1)
};
+ if (!reader.IsDBNull(2))
+ {
+ info.ItemName = reader.GetString(2);
+ }
+
if (!reader.IsDBNull(3))
{
- info.OutputPath = reader.GetString(3);
+ info.MediaSourceId = reader.GetString(3);
}
- if (!reader.IsDBNull(4))
+ info.JobId = reader.GetString(4);
+
+ if (!reader.IsDBNull(5))
+ {
+ info.OutputPath = reader.GetString(5);
+ }
+
+ if (!reader.IsDBNull(6))
{
- info.Status = (SyncJobItemStatus)Enum.Parse(typeof(SyncJobItemStatus), reader.GetString(4), true);
+ info.Status = (SyncJobItemStatus)Enum.Parse(typeof(SyncJobItemStatus), reader.GetString(6), true);
}
- info.TargetId = reader.GetString(5);
+ info.TargetId = reader.GetString(7);
- info.DateCreated = reader.GetDateTime(6);
+ info.DateCreated = reader.GetDateTime(8);
- if (!reader.IsDBNull(7))
+ if (!reader.IsDBNull(9))
{
- info.Progress = reader.GetDouble(7);
+ info.Progress = reader.GetDouble(9);
}
-
+
return info;
}
diff --git a/MediaBrowser.Server.Implementations/Sync/SyncScheduledTask.cs b/MediaBrowser.Server.Implementations/Sync/SyncScheduledTask.cs
index df03ab6f7..c2925551b 100644
--- a/MediaBrowser.Server.Implementations/Sync/SyncScheduledTask.cs
+++ b/MediaBrowser.Server.Implementations/Sync/SyncScheduledTask.cs
@@ -1,6 +1,8 @@
using MediaBrowser.Common.ScheduledTasks;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Sync;
+using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
@@ -16,19 +18,23 @@ namespace MediaBrowser.Server.Implementations.Sync
private readonly ISyncManager _syncManager;
private readonly ILogger _logger;
private readonly IUserManager _userManager;
+ private readonly ITVSeriesManager _tvSeriesManager;
+ private readonly IMediaEncoder _mediaEncoder;
- public SyncScheduledTask(ILibraryManager libraryManager, ISyncRepository syncRepo, ISyncManager syncManager, ILogger logger, IUserManager userManager)
+ public SyncScheduledTask(ILibraryManager libraryManager, ISyncRepository syncRepo, ISyncManager syncManager, ILogger logger, IUserManager userManager, ITVSeriesManager tvSeriesManager, IMediaEncoder mediaEncoder)
{
_libraryManager = libraryManager;
_syncRepo = syncRepo;
_syncManager = syncManager;
_logger = logger;
_userManager = userManager;
+ _tvSeriesManager = tvSeriesManager;
+ _mediaEncoder = mediaEncoder;
}
public string Name
{
- get { return "Sync"; }
+ get { return "Sync preparation"; }
}
public string Description
@@ -46,7 +52,7 @@ namespace MediaBrowser.Server.Implementations.Sync
public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
- return new SyncJobProcessor(_libraryManager, _syncRepo, _syncManager, _logger, _userManager).Sync(progress,
+ return new SyncJobProcessor(_libraryManager, _syncRepo, _syncManager, _logger, _userManager, _tvSeriesManager, _mediaEncoder).Sync(progress,
cancellationToken);
}
diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config
index 0c79ba387..239bca405 100644
--- a/MediaBrowser.Server.Implementations/packages.config
+++ b/MediaBrowser.Server.Implementations/packages.config
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
- <package id="MediaBrowser.Naming" version="1.0.0.18" targetFramework="net45" />
+ <package id="MediaBrowser.Naming" version="1.0.0.24" targetFramework="net45" />
<package id="Mono.Nat" version="1.2.21.0" targetFramework="net45" />
<package id="morelinq" version="1.1.0" targetFramework="net45" />
</packages> \ No newline at end of file
diff --git a/MediaBrowser.Server.Mac/Native/BaseMonoApp.cs b/MediaBrowser.Server.Mac/Native/BaseMonoApp.cs
index a2c860413..576b5b75f 100644
--- a/MediaBrowser.Server.Mac/Native/BaseMonoApp.cs
+++ b/MediaBrowser.Server.Mac/Native/BaseMonoApp.cs
@@ -67,7 +67,7 @@ namespace MediaBrowser.Server.Mac
return list;
}
- public void AuthorizeServer(int httpServerPort, string httpServerUrlPrefix, int udpPort, string tempDirectory)
+ public void AuthorizeServer(int udpPort, int httpServerPort, string tempDirectory)
{
}
diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
index c53654e4f..e780f447f 100644
--- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
+++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
@@ -131,10 +131,6 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
- <None Include="..\ThirdParty\libgdiplus\osx\libgdiplus.dylib">
- <Link>libgdiplus.dylib</Link>
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
<None Include="..\ThirdParty\libwebp\osx\libwebp.5.dylib">
<Link>libwebp\osx\libwebp.5.dylib</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
diff --git a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs
index 39d2d52d7..7f61570a4 100644
--- a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs
+++ b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs
@@ -83,7 +83,7 @@ namespace MediaBrowser.Server.Mono.Native
return list;
}
- public void AuthorizeServer(int httpServerPort, string httpServerUrlPrefix, int udpPort, string tempDirectory)
+ public void AuthorizeServer(int udpPort, int httpServerPort, string tempDirectory)
{
}
diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs
index eca600b33..bbdb89ac3 100644
--- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs
+++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs
@@ -185,6 +185,7 @@ namespace MediaBrowser.Server.Startup.Common
/// </summary>
/// <value>The media encoder.</value>
private IMediaEncoder MediaEncoder { get; set; }
+ private ISubtitleEncoder SubtitleEncoder { get; set; }
private IConnectManager ConnectManager { get; set; }
private ISessionManager SessionManager { get; set; }
@@ -224,7 +225,7 @@ namespace MediaBrowser.Server.Startup.Common
private readonly StartupOptions _startupOptions;
private readonly string _remotePackageName;
- private readonly bool _supportsNativeWebSocket;
+ private bool _supportsNativeWebSocket;
internal INativeApp NativeApp { get; set; }
@@ -238,12 +239,12 @@ namespace MediaBrowser.Server.Startup.Common
/// <param name="remotePackageName">Name of the remote package.</param>
/// <param name="supportsNativeWebSocket">if set to <c>true</c> [supports native web socket].</param>
/// <param name="nativeApp">The native application.</param>
- public ApplicationHost(ServerApplicationPaths applicationPaths,
- ILogManager logManager,
- StartupOptions options,
+ public ApplicationHost(ServerApplicationPaths applicationPaths,
+ ILogManager logManager,
+ StartupOptions options,
IFileSystem fileSystem,
- string remotePackageName,
- bool supportsNativeWebSocket,
+ string remotePackageName,
+ bool supportsNativeWebSocket,
INativeApp nativeApp)
: base(applicationPaths, logManager, fileSystem)
{
@@ -353,17 +354,18 @@ namespace MediaBrowser.Server.Startup.Common
public override async Task Init(IProgress<double> progress)
{
- PerformVersionMigration();
+ PerformPreInitMigrations();
await base.Init(progress).ConfigureAwait(false);
+
+ PerformPostInitMigrations();
}
- private void PerformVersionMigration()
+ private void PerformPreInitMigrations()
{
var migrations = new List<IVersionMigration>
{
new MigrateUserFolders(ApplicationPaths),
- new PlaylistImages(ServerConfigurationManager),
new RenameXbmcOptions(ServerConfigurationManager),
new RenameXmlOptions(ServerConfigurationManager),
new DeprecatePlugins(ApplicationPaths),
@@ -376,6 +378,19 @@ namespace MediaBrowser.Server.Startup.Common
}
}
+ private void PerformPostInitMigrations()
+ {
+ var migrations = new List<IVersionMigration>
+ {
+ new MigrateTranscodingPath(ServerConfigurationManager)
+ };
+
+ foreach (var task in migrations)
+ {
+ task.Run();
+ }
+ }
+
/// <summary>
/// Registers resources that classes will depend on
/// </summary>
@@ -384,7 +399,7 @@ namespace MediaBrowser.Server.Startup.Common
{
await base.RegisterResources(progress).ConfigureAwait(false);
- RegisterSingleInstance<IHttpResultFactory>(new HttpResultFactory(LogManager, FileSystemManager, JsonSerializer));
+ RegisterSingleInstance<IHttpResultFactory>(new HttpResultFactory(LogManager, FileSystemManager, JsonSerializer));
RegisterSingleInstance<IServerApplicationHost>(this);
RegisterSingleInstance<IServerApplicationPaths>(ApplicationPaths);
@@ -396,10 +411,10 @@ namespace MediaBrowser.Server.Startup.Common
RegisterSingleInstance<IBlurayExaminer>(() => new BdInfoExaminer());
- UserDataManager = new UserDataManager(LogManager);
+ UserDataManager = new UserDataManager(LogManager, ServerConfigurationManager);
RegisterSingleInstance(UserDataManager);
- UserRepository = await GetUserRepository().ConfigureAwait(false);
+ UserRepository = await GetUserRepository().ConfigureAwait(false);
RegisterSingleInstance(UserRepository);
DisplayPreferencesRepository = new SqliteDisplayPreferencesRepository(ApplicationPaths, JsonSerializer, LogManager);
@@ -420,7 +435,7 @@ namespace MediaBrowser.Server.Startup.Common
SyncRepository = await GetSyncRepository().ConfigureAwait(false);
RegisterSingleInstance(SyncRepository);
- UserManager = new UserManager(LogManager.GetLogger("UserManager"), ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, () => ConnectManager, this);
+ UserManager = new UserManager(LogManager.GetLogger("UserManager"), ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, () => ConnectManager, this, JsonSerializer);
RegisterSingleInstance(UserManager);
LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager);
@@ -440,7 +455,19 @@ namespace MediaBrowser.Server.Startup.Common
RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LogManager, LibraryManager, UserManager));
- HttpServer = ServerFactory.CreateServer(this, LogManager, "Media Browser", WebApplicationName, "dashboard/index.html", _supportsNativeWebSocket);
+ if (IsFirstRun)
+ {
+ ServerConfigurationManager.Configuration.EnableWin8HttpListener = false;
+ ServerConfigurationManager.SaveConfiguration();
+ _supportsNativeWebSocket = false;
+ }
+
+ if (!ServerConfigurationManager.Configuration.EnableWin8HttpListener)
+ {
+ _supportsNativeWebSocket = false;
+ }
+
+ HttpServer = ServerFactory.CreateServer(this, LogManager, "Media Browser", WebApplicationName, "dashboard/index.html", _supportsNativeWebSocket);
RegisterSingleInstance(HttpServer, false);
progress.Report(10);
@@ -450,13 +477,13 @@ namespace MediaBrowser.Server.Startup.Common
var innerProgress = new ActionableProgress<double>();
innerProgress.RegisterAction(p => progress.Report((.75 * p) + 15));
- await RegisterMediaEncoder(innerProgress).ConfigureAwait(false);
- progress.Report(90);
-
ImageProcessor = new ImageProcessor(LogManager.GetLogger("ImageProcessor"), ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer, MediaEncoder);
RegisterSingleInstance(ImageProcessor);
- SyncManager = new SyncManager(LibraryManager, SyncRepository, ImageProcessor, LogManager.GetLogger("SyncManager"), UserManager);
+ TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager);
+ RegisterSingleInstance(TVSeriesManager);
+
+ SyncManager = new SyncManager(LibraryManager, SyncRepository, ImageProcessor, LogManager.GetLogger("SyncManager"), UserManager, () => DtoService, this, TVSeriesManager, () => MediaEncoder);
RegisterSingleInstance(SyncManager);
DtoService = new DtoService(Logger, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager, SyncManager, this);
@@ -471,7 +498,7 @@ namespace MediaBrowser.Server.Startup.Common
DeviceManager = new DeviceManager(new DeviceRepository(ApplicationPaths, JsonSerializer, Logger), UserManager, FileSystemManager, LibraryMonitor, ConfigurationManager, LogManager.GetLogger("DeviceManager"));
RegisterSingleInstance(DeviceManager);
- SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, ItemRepository, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager);
+ SessionManager = new SessionManager(UserDataManager, Logger, UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, ItemRepository, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager);
RegisterSingleInstance(SessionManager);
var newsService = new Implementations.News.NewsService(ApplicationPaths, JsonSerializer);
@@ -485,9 +512,6 @@ namespace MediaBrowser.Server.Startup.Common
ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, Logger, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient);
RegisterSingleInstance(ChannelManager);
- TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager);
- RegisterSingleInstance(TVSeriesManager);
-
var appThemeManager = new AppThemeManager(ApplicationPaths, FileSystemManager, JsonSerializer, Logger);
RegisterSingleInstance<IAppThemeManager>(appThemeManager);
@@ -521,6 +545,9 @@ namespace MediaBrowser.Server.Startup.Common
ChapterManager = new ChapterManager(LibraryManager, LogManager.GetLogger("ChapterManager"), ServerConfigurationManager, ItemRepository);
RegisterSingleInstance(ChapterManager);
+ await RegisterMediaEncoder(innerProgress).ConfigureAwait(false);
+ progress.Report(90);
+
EncodingManager = new EncodingManager(FileSystemManager, Logger,
MediaEncoder, ChapterManager);
RegisterSingleInstance(EncodingManager);
@@ -532,14 +559,15 @@ namespace MediaBrowser.Server.Startup.Common
var authContext = new AuthorizationContext(AuthenticationRepository);
RegisterSingleInstance<IAuthorizationContext>(authContext);
RegisterSingleInstance<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
- RegisterSingleInstance<IAuthService>(new AuthService(UserManager, authContext, ServerConfigurationManager, ConnectManager, SessionManager));
+ RegisterSingleInstance<IAuthService>(new AuthService(UserManager, authContext, ServerConfigurationManager, ConnectManager, SessionManager, DeviceManager));
- RegisterSingleInstance<ISubtitleEncoder>(new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer));
+ SubtitleEncoder = new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer);
+ RegisterSingleInstance(SubtitleEncoder);
await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false);
await ConfigureItemRepositories().ConfigureAwait(false);
await ConfigureUserDataRepositories().ConfigureAwait(false);
- await ConfigureNotificationsRepository().ConfigureAwait(false);
+ await ConfigureNotificationsRepository().ConfigureAwait(false);
progress.Report(100);
SetStaticProperties();
@@ -565,7 +593,19 @@ namespace MediaBrowser.Server.Startup.Common
new FFmpegValidator(Logger, ApplicationPaths).Validate(info);
- MediaEncoder = new MediaEncoder(LogManager.GetLogger("MediaEncoder"), JsonSerializer, info.EncoderPath, info.ProbePath, info.Version);
+ MediaEncoder = new MediaEncoder(LogManager.GetLogger("MediaEncoder"),
+ JsonSerializer,
+ info.EncoderPath,
+ info.ProbePath,
+ info.Version,
+ ServerConfigurationManager,
+ FileSystemManager,
+ LiveTvManager,
+ IsoManager,
+ LibraryManager,
+ ChannelManager,
+ SessionManager,
+ () => SubtitleEncoder);
RegisterSingleInstance(MediaEncoder);
}
@@ -710,7 +750,14 @@ namespace MediaBrowser.Server.Startup.Common
/// </summary>
protected override void FindParts()
{
- if (IsFirstRun)
+ // TODO: Remove after next release
+ if (!IsFirstRun && !ServerConfigurationManager.Configuration.IsPortAuthorized)
+ {
+ ServerConfigurationManager.Configuration.IsPortAuthorized = true;
+ ConfigurationManager.SaveConfiguration();
+ }
+
+ if (!ServerConfigurationManager.Configuration.IsPortAuthorized)
{
RegisterServerWithAdministratorAccess();
}
@@ -721,7 +768,7 @@ namespace MediaBrowser.Server.Startup.Common
ServerManager.AddWebSocketListeners(GetExports<IWebSocketListener>(false));
- StartServer(true);
+ StartServer();
LibraryManager.AddParts(GetExports<IResolverIgnoreRule>(),
GetExports<IVirtualFolderCreator>(),
@@ -759,8 +806,7 @@ namespace MediaBrowser.Server.Startup.Common
/// <summary>
/// Starts the server.
/// </summary>
- /// <param name="retryOnFailure">if set to <c>true</c> [retry on failure].</param>
- private void StartServer(bool retryOnFailure)
+ private void StartServer()
{
try
{
@@ -770,16 +816,7 @@ namespace MediaBrowser.Server.Startup.Common
{
Logger.ErrorException("Error starting http server", ex);
- if (retryOnFailure)
- {
- RegisterServerWithAdministratorAccess();
-
- StartServer(false);
- }
- else
- {
- throw;
- }
+ throw;
}
}
@@ -794,6 +831,9 @@ namespace MediaBrowser.Server.Startup.Common
if (!HttpServer.UrlPrefixes.SequenceEqual(HttpServerUrlPrefixes, StringComparer.OrdinalIgnoreCase))
{
+ ServerConfigurationManager.Configuration.IsPortAuthorized = false;
+ ServerConfigurationManager.SaveConfiguration();
+
NotifyPendingRestart();
}
}
@@ -1056,9 +1096,8 @@ namespace MediaBrowser.Server.Startup.Common
try
{
NativeApp.AuthorizeServer(
- ServerConfigurationManager.Configuration.HttpServerPortNumber,
- HttpServerUrlPrefixes.First(),
UdpServerEntryPoint.PortNumber,
+ ServerConfigurationManager.Configuration.HttpServerPortNumber,
ConfigurationManager.CommonApplicationPaths.TempDirectory);
}
catch (Exception ex)
diff --git a/MediaBrowser.Server.Startup.Common/INativeApp.cs b/MediaBrowser.Server.Startup.Common/INativeApp.cs
index 5042e1cea..4abea57fb 100644
--- a/MediaBrowser.Server.Startup.Common/INativeApp.cs
+++ b/MediaBrowser.Server.Startup.Common/INativeApp.cs
@@ -16,11 +16,10 @@ namespace MediaBrowser.Server.Startup.Common
/// <summary>
/// Authorizes the server.
/// </summary>
- /// <param name="httpServerPort">The HTTP server port.</param>
- /// <param name="httpServerUrlPrefix">The HTTP server URL prefix.</param>
/// <param name="udpPort">The UDP port.</param>
+ /// <param name="httpServerPort">The HTTP server port.</param>
/// <param name="tempDirectory">The temporary directory.</param>
- void AuthorizeServer(int httpServerPort, string httpServerUrlPrefix, int udpPort, string tempDirectory);
+ void AuthorizeServer(int udpPort, int httpServerPort, string tempDirectory);
/// <summary>
/// Gets the environment.
diff --git a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
index b133f78e7..38e07fde4 100644
--- a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
+++ b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
@@ -66,8 +66,8 @@
<Compile Include="Migrations\DeleteDlnaProfiles.cs" />
<Compile Include="Migrations\DeprecatePlugins.cs" />
<Compile Include="Migrations\IVersionMigration.cs" />
+ <Compile Include="Migrations\MigrateTranscodingPath.cs" />
<Compile Include="Migrations\MigrateUserFolders.cs" />
- <Compile Include="Migrations\PlaylistImages.cs" />
<Compile Include="Migrations\RenameXbmcOptions.cs" />
<Compile Include="Migrations\RenameXmlOptions.cs" />
<Compile Include="NativeEnvironment.cs" />
diff --git a/MediaBrowser.Server.Startup.Common/Migrations/MigrateTranscodingPath.cs b/MediaBrowser.Server.Startup.Common/Migrations/MigrateTranscodingPath.cs
new file mode 100644
index 000000000..88f60841d
--- /dev/null
+++ b/MediaBrowser.Server.Startup.Common/Migrations/MigrateTranscodingPath.cs
@@ -0,0 +1,30 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Model.Configuration;
+
+namespace MediaBrowser.Server.Startup.Common.Migrations
+{
+ public class MigrateTranscodingPath : IVersionMigration
+ {
+ private readonly IServerConfigurationManager _config;
+
+ public MigrateTranscodingPath(IServerConfigurationManager config)
+ {
+ _config = config;
+ }
+
+ public void Run()
+ {
+ if (!string.IsNullOrWhiteSpace(_config.Configuration.TranscodingTempPath))
+ {
+ var newConfig = _config.GetConfiguration<EncodingOptions>("encoding");
+
+ newConfig.TranscodingTempPath = _config.Configuration.TranscodingTempPath;
+ _config.SaveConfiguration("encoding", newConfig);
+
+ _config.Configuration.TranscodingTempPath = null;
+ _config.SaveConfiguration();
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Startup.Common/Migrations/PlaylistImages.cs b/MediaBrowser.Server.Startup.Common/Migrations/PlaylistImages.cs
deleted file mode 100644
index f6ddf5847..000000000
--- a/MediaBrowser.Server.Startup.Common/Migrations/PlaylistImages.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using System.IO;
-using System.Linq;
-
-namespace MediaBrowser.Server.Startup.Common.Migrations
-{
- public class PlaylistImages : IVersionMigration
- {
- private readonly IServerConfigurationManager _config;
-
- public PlaylistImages(IServerConfigurationManager config)
- {
- _config = config;
- }
-
- public void Run()
- {
- if (!_config.Configuration.PlaylistImagesDeleted)
- {
- DeletePlaylistImages();
- _config.Configuration.PlaylistImagesDeleted = true;
- _config.SaveConfiguration();
- }
- }
-
- private void DeletePlaylistImages()
- {
- try
- {
- var path = Path.Combine(_config.ApplicationPaths.DataPath, "playlists");
-
- var files = Directory.GetFiles(path, "*", SearchOption.AllDirectories)
- .Where(i => BaseItem.SupportedImageExtensions.Contains(Path.GetExtension(i) ?? string.Empty))
- .ToList();
-
- foreach (var file in files)
- {
- try
- {
- File.Delete(file);
- }
- catch (IOException)
- {
-
- }
- }
- }
- catch (IOException)
- {
-
- }
- }
- }
-}
diff --git a/MediaBrowser.Server.Startup.Common/Migrations/RenameXmlOptions.cs b/MediaBrowser.Server.Startup.Common/Migrations/RenameXmlOptions.cs
index a955b57ea..be8ae2f81 100644
--- a/MediaBrowser.Server.Startup.Common/Migrations/RenameXmlOptions.cs
+++ b/MediaBrowser.Server.Startup.Common/Migrations/RenameXmlOptions.cs
@@ -42,9 +42,9 @@ namespace MediaBrowser.Server.Startup.Common.Migrations
{
for (var i = 0; i < options.Length; i++)
{
- if (string.Equals(options[i], "Media Browser Xml", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(options[i], "Media Browser Legacy Xml", StringComparison.OrdinalIgnoreCase))
{
- options[i] = "Media Browser Legacy Xml";
+ options[i] = "Media Browser Xml";
changed = true;
}
}
diff --git a/MediaBrowser.ServerApplication/Native/RegisterServer.bat b/MediaBrowser.ServerApplication/Native/RegisterServer.bat
index 350412344..3346ecb31 100644
--- a/MediaBrowser.ServerApplication/Native/RegisterServer.bat
+++ b/MediaBrowser.ServerApplication/Native/RegisterServer.bat
@@ -1,21 +1,15 @@
-rem %1 = http server port
-rem %2 = http server url
-rem %3 = udp server port
+rem %1 = udp server port
+rem %2 = http server port
if [%1]==[] GOTO DONE
-netsh advfirewall firewall delete rule name="Port %1" protocol=TCP localport=%1
-netsh advfirewall firewall add rule name="Port %1" dir=in action=allow protocol=TCP localport=%1
+netsh advfirewall firewall delete rule name="Port %1" protocol=UDP localport=%1
+netsh advfirewall firewall add rule name="Port %1" dir=in action=allow protocol=UDP localport=%1
if [%2]==[] GOTO DONE
-netsh http del urlacl url="%2" user="NT AUTHORITY\Authenticated Users"
-netsh http add urlacl url="%2" user="NT AUTHORITY\Authenticated Users"
-
-if [%3]==[] GOTO DONE
-
-netsh advfirewall firewall delete rule name="Port %3" protocol=UDP localport=%3
-netsh advfirewall firewall add rule name="Port %3" dir=in action=allow protocol=UDP localport=%3
+netsh advfirewall firewall delete rule name="Port %2" protocol=TCP localport=%2
+netsh advfirewall firewall add rule name="Port %2" dir=in action=allow protocol=TCP localport=%2
:DONE
diff --git a/MediaBrowser.ServerApplication/Native/ServerAuthorization.cs b/MediaBrowser.ServerApplication/Native/ServerAuthorization.cs
index e5989db3b..d9063aa58 100644
--- a/MediaBrowser.ServerApplication/Native/ServerAuthorization.cs
+++ b/MediaBrowser.ServerApplication/Native/ServerAuthorization.cs
@@ -13,11 +13,10 @@ namespace MediaBrowser.ServerApplication.Native
/// <summary>
/// Authorizes the server.
/// </summary>
- /// <param name="httpServerPort">The HTTP server port.</param>
- /// <param name="httpServerUrlPrefix">The HTTP server URL prefix.</param>
/// <param name="udpPort">The UDP port.</param>
+ /// <param name="httpServerPort">The HTTP server port.</param>
/// <param name="tempDirectory">The temp directory.</param>
- public static void AuthorizeServer(int httpServerPort, string httpServerUrlPrefix, int udpPort, string tempDirectory)
+ public static void AuthorizeServer(int udpPort, int httpServerPort, string tempDirectory)
{
Directory.CreateDirectory(tempDirectory);
@@ -37,9 +36,7 @@ namespace MediaBrowser.ServerApplication.Native
{
FileName = tmpFile,
- Arguments = string.Format("{0} {1} {2}", httpServerPort,
- httpServerUrlPrefix,
- udpPort),
+ Arguments = string.Format("{0} {1}", udpPort, httpServerPort),
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
diff --git a/MediaBrowser.ServerApplication/Native/WindowsApp.cs b/MediaBrowser.ServerApplication/Native/WindowsApp.cs
index 9a37c268c..0970d6537 100644
--- a/MediaBrowser.ServerApplication/Native/WindowsApp.cs
+++ b/MediaBrowser.ServerApplication/Native/WindowsApp.cs
@@ -21,9 +21,9 @@ namespace MediaBrowser.ServerApplication.Native
return list;
}
- public void AuthorizeServer(int httpServerPort, string httpServerUrlPrefix, int udpPort, string tempDirectory)
+ public void AuthorizeServer(int httpServerPort, int udpPort, string tempDirectory)
{
- ServerAuthorization.AuthorizeServer(httpServerPort, httpServerUrlPrefix, udpPort, tempDirectory);
+ ServerAuthorization.AuthorizeServer(udpPort, httpServerPort, tempDirectory);
}
public NativeEnvironment Environment
diff --git a/MediaBrowser.Tests/MediaBrowser.Tests.csproj b/MediaBrowser.Tests/MediaBrowser.Tests.csproj
index f93a2612a..e9cec61d6 100644
--- a/MediaBrowser.Tests/MediaBrowser.Tests.csproj
+++ b/MediaBrowser.Tests/MediaBrowser.Tests.csproj
@@ -53,7 +53,6 @@
<Compile Include="MediaEncoding\Subtitles\AssParserTests.cs" />
<Compile Include="MediaEncoding\Subtitles\SrtParserTests.cs" />
<Compile Include="MediaEncoding\Subtitles\VttWriterTest.cs" />
- <Compile Include="Resolvers\TvUtilTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
diff --git a/MediaBrowser.Tests/Resolvers/TvUtilTests.cs b/MediaBrowser.Tests/Resolvers/TvUtilTests.cs
deleted file mode 100644
index ec9356dad..000000000
--- a/MediaBrowser.Tests/Resolvers/TvUtilTests.cs
+++ /dev/null
@@ -1,246 +0,0 @@
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Server.Implementations.Library.Resolvers.TV;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-namespace MediaBrowser.Tests.Resolvers
-{
- [TestClass]
- public class TvUtilTests
- {
- [TestMethod]
- public void TestGetEpisodeNumberFromFile()
- {
- Assert.AreEqual(03, SeriesResolver.GetEpisodeNumberFromFile(@"Season 02\S02E03 blah.avi", true));
-
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"Season 1\01x02 blah.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"Season 1\S01x02 blah.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"Season 1\S01E02 blah.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"Season 1\S01xE02 blah.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"Season 1\seriesname 01x02 blah.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"Season 1\seriesname S01x02 blah.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"Season 1\seriesname S01E02 blah.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"Season 1\seriesname S01xE02 blah.avi", true));
- Assert.AreEqual(03, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2\Elementary - 02x03 - 02x04 - 02x15 - Ep Name.ext", true));
- Assert.AreEqual(03, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2\02x03 - 02x04 - 02x15 - Ep Name.ext", true));
- Assert.AreEqual(03, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2\02x03-04-15 - Ep Name.ext", true));
- Assert.AreEqual(03, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2\Elementary - 02x03-04-15 - Ep Name.ext", true));
- Assert.AreEqual(03, SeriesResolver.GetEpisodeNumberFromFile(@"Season 02\02x03-E15 - Ep Name.ext", true));
- Assert.AreEqual(03, SeriesResolver.GetEpisodeNumberFromFile(@"Season 02\Elementary - 02x03-E15 - Ep Name.ext", true));
- Assert.AreEqual(03, SeriesResolver.GetEpisodeNumberFromFile(@"Season 02\02x03 - x04 - x15 - Ep Name.ext", true));
- Assert.AreEqual(03, SeriesResolver.GetEpisodeNumberFromFile(@"Season 02\Elementary - 02x03 - x04 - x15 - Ep Name.ext", true));
- Assert.AreEqual(03, SeriesResolver.GetEpisodeNumberFromFile(@"Season 02\02x03x04x15 - Ep Name.ext", true));
- Assert.AreEqual(03, SeriesResolver.GetEpisodeNumberFromFile(@"Season 02\Elementary - 02x03x04x15 - Ep Name.ext", true));
- Assert.AreEqual(23, SeriesResolver.GetEpisodeNumberFromFile(@"Season 1\Elementary - S01E23-E24-E26 - The Woman.mp4", true));
- Assert.AreEqual(23, SeriesResolver.GetEpisodeNumberFromFile(@"Season 1\S01E23-E24-E26 - The Woman.mp4", true));
- Assert.AreEqual(9, SeriesResolver.GetEpisodeNumberFromFile(@"Season 25\The Simpsons.S25E09.Steal this episode.mp4", true));
- Assert.AreEqual(8, SeriesResolver.GetEpisodeNumberFromFile(@"The Simpsons\The Simpsons.S25E08.Steal this episode.mp4", false));
- Assert.AreEqual(136, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2\[HorribleSubs] Hunter X Hunter - 136 [720p].mkv",true));
-
- //Four Digits seasons
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2009\2009x02 blah.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2009\S2009x02 blah.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2009\S2009E02 blah.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2009\S2009xE02 blah.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2009\seriesname 2009x02 blah.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2009\seriesname S2009x02 blah.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2009\seriesname S2009E02 blah.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2009\seriesname S2009xE02 blah.avi", true));
- Assert.AreEqual(03, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2009\Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.ext", true));
- Assert.AreEqual(03, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2009\2009x03 - 2009x04 - 2009x15 - Ep Name.ext", true));
- Assert.AreEqual(03, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2009\2009x03-04-15 - Ep Name.ext", true));
- Assert.AreEqual(03, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2009\Elementary - 2009x03-04-15 - Ep Name.ext", true));
- Assert.AreEqual(03, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2009\2009x03-E15 - Ep Name.ext", true));
- Assert.AreEqual(03, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2009\Elementary - 2009x03-E15 - Ep Name.ext", true));
- Assert.AreEqual(03, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2009\2009x03 - x04 - x15 - Ep Name.ext", true));
- Assert.AreEqual(03, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2009\Elementary - 2009x03 - x04 - x15 - Ep Name.ext", true));
- Assert.AreEqual(03, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2009\2009x03x04x15 - Ep Name.ext", true));
- Assert.AreEqual(03, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2009\Elementary - 2009x03x04x15 - Ep Name.ext", true));
- Assert.AreEqual(23, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2009\Elementary - S2009E23-E24-E26 - The Woman.mp4", true));
- Assert.AreEqual(23, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2009\S2009E23-E24-E26 - The Woman.mp4", true));
-
- //Without season number
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"Season 1\02 - blah.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2\02 - blah 14 blah.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"Season 1\02 - blah-02 a.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"Season 2\02.avi", true));
-
- //Without seasons
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"The Simpsons\02.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"The Simpsons\02 - Ep Name.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"The Simpsons\02-Ep Name.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"The Simpsons\02.EpName.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"The Simpsons\The Simpsons - 02.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"The Simpsons\The Simpsons - 02 - Ep Name.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"The Simpsons\The Simpsons - 02 Ep Name.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"The Simpsons\The Simpsons 5 - 02 - Ep Name.avi", true));
- Assert.AreEqual(02, SeriesResolver.GetEpisodeNumberFromFile(@"The Simpsons\The Simpsons 5 - 02 Ep Name.avi", true));
- }
-
- [TestMethod]
- public void TestGetEndingEpisodeNumberFromFile()
- {
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 1\4x01 20 Hours in America (1).mkv"));
-
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 1\01x02 blah.avi"));
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 1\S01x02 blah.avi"));
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 1\S01E02 blah.avi"));
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 1\S01xE02 blah.avi"));
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 1\seriesname 01x02 blah.avi"));
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 1\seriesname S01x02 blah.avi"));
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 1\seriesname S01E02 blah.avi"));
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 1\seriesname S01xE02 blah.avi"));
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2\02x03 - 04 Ep Name.ext"));
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2\My show name 02x03 - 04 Ep Name.ext"));
- Assert.AreEqual(15, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2\Elementary - 02x03 - 02x04 - 02x15 - Ep Name.ext"));
- Assert.AreEqual(15, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2\02x03 - 02x04 - 02x15 - Ep Name.ext"));
- Assert.AreEqual(15, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2\02x03-04-15 - Ep Name.ext"));
- Assert.AreEqual(15, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2\Elementary - 02x03-04-15 - Ep Name.ext"));
- Assert.AreEqual(15, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 02\02x03-E15 - Ep Name.ext"));
- Assert.AreEqual(15, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 02\Elementary - 02x03-E15 - Ep Name.ext"));
- Assert.AreEqual(15, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 02\02x03 - x04 - x15 - Ep Name.ext"));
- Assert.AreEqual(15, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 02\Elementary - 02x03 - x04 - x15 - Ep Name.ext"));
- Assert.AreEqual(15, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 02\02x03x04x15 - Ep Name.ext"));
- Assert.AreEqual(15, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 02\Elementary - 02x03x04x15 - Ep Name.ext"));
- Assert.AreEqual(26, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 1\Elementary - S01E23-E24-E26 - The Woman.mp4"));
- Assert.AreEqual(26, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 1\S01E23-E24-E26 - The Woman.mp4"));
-
-
- //Four Digits seasons
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2009\2009x02 blah.avi"));
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2009\S2009x02 blah.avi"));
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2009\S2009E02 blah.avi"));
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2009\S2009xE02 blah.avi"));
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2009\seriesname 2009x02 blah.avi"));
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2009\seriesname S2009x02 blah.avi"));
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2009\seriesname S2009E02 blah.avi"));
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2009\seriesname S2009xE02 blah.avi"));
- Assert.AreEqual(15, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2009\Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.ext"));
- Assert.AreEqual(15, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2009\2009x03 - 2009x04 - 2009x15 - Ep Name.ext"));
- Assert.AreEqual(15, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2009\2009x03-04-15 - Ep Name.ext"));
- Assert.AreEqual(15, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2009\Elementary - 2009x03-04-15 - Ep Name.ext"));
- Assert.AreEqual(15, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2009\2009x03-E15 - Ep Name.ext"));
- Assert.AreEqual(15, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2009\Elementary - 2009x03-E15 - Ep Name.ext"));
- Assert.AreEqual(15, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2009\2009x03 - x04 - x15 - Ep Name.ext"));
- Assert.AreEqual(15, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2009\Elementary - 2009x03 - x04 - x15 - Ep Name.ext"));
- Assert.AreEqual(15, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2009\2009x03x04x15 - Ep Name.ext"));
- Assert.AreEqual(15, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2009\Elementary - 2009x03x04x15 - Ep Name.ext"));
- Assert.AreEqual(26, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2009\Elementary - S2009E23-E24-E26 - The Woman.mp4"));
- Assert.AreEqual(26, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2009\S2009E23-E24-E26 - The Woman.mp4"));
-
- //Without season number
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 1\02 - blah.avi"));
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2\02 - blah 14 blah.avi"));
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 1\02 - blah-02 a.avi"));
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2\02.avi"));
-
- Assert.AreEqual(3, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 1\02-03 - blah.avi"));
- Assert.AreEqual(4, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2\02-04 - blah 14 blah.avi"));
- Assert.AreEqual(5, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 1\02-05 - blah-02 a.avi"));
- Assert.AreEqual(4, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2\02-04.avi"));
- Assert.AreEqual(null, SeriesResolver.GetEndingEpisodeNumberFromFile(@"Season 2\[HorribleSubs] Hunter X Hunter - 136 [720p].mkv"));
-
- }
-
- [TestMethod]
- public void TestGetSeasonNumberFromPath() {
-
- Assert.AreEqual(02, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"\Show\Season 02\S02E03 blah.avi"));
-
- Assert.AreEqual(1, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 1"));
- Assert.AreEqual(1, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 1"));
- Assert.AreEqual(1, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 1"));
- Assert.AreEqual(1, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 1"));
- Assert.AreEqual(1, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 1"));
- Assert.AreEqual(1, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 1"));
- Assert.AreEqual(1, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 1"));
- Assert.AreEqual(1, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 1"));
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2"));
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2"));
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2"));
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2"));
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 02"));
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 02"));
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 02"));
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 02"));
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 02"));
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 02"));
- Assert.AreEqual(1, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 1"));
- Assert.AreEqual(1, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 1"));
-
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Seinfeld\S02"));
-
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Seinfeld\2"));
-
- //Four Digits seasons
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2009"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2009"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2009"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2009"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2009"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2009"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2009"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2009"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2009"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2009"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2009"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2009"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2009"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2009"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2009"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2009"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2009"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2009"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2009"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromPath(@"\Drive\Season 2009"));
- }
-
- [TestMethod]
- public void TestGetSeasonNumberFromEpisodeFile()
- {
- Assert.AreEqual(1, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 1\01x02 blah.avi"));
- Assert.AreEqual(1, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 1\S01x02 blah.avi"));
- Assert.AreEqual(1, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 1\S01E02 blah.avi"));
- Assert.AreEqual(1, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 1\S01xE02 blah.avi"));
- Assert.AreEqual(1, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 1\seriesname 01x02 blah.avi"));
- Assert.AreEqual(1, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 1\seriesname S01x02 blah.avi"));
- Assert.AreEqual(1, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 1\seriesname S01E02 blah.avi"));
- Assert.AreEqual(1, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 1\seriesname S01xE02 blah.avi"));
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2\Elementary - 02x03 - 02x04 - 02x15 - Ep Name.ext"));
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2\02x03 - 02x04 - 02x15 - Ep Name.ext"));
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2\02x03-04-15 - Ep Name.ext"));
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2\Elementary - 02x03-04-15 - Ep Name.ext"));
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 02\02x03-E15 - Ep Name.ext"));
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 02\Elementary - 02x03-E15 - Ep Name.ext"));
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 02\02x03 - x04 - x15 - Ep Name.ext"));
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 02\Elementary - 02x03 - x04 - x15 - Ep Name.ext"));
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 02\02x03x04x15 - Ep Name.ext"));
- Assert.AreEqual(2, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 02\Elementary - 02x03x04x15 - Ep Name.ext"));
- Assert.AreEqual(1, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 1\Elementary - S01E23-E24-E26 - The Woman.mp4"));
- Assert.AreEqual(1, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 1\S01E23-E24-E26 - The Woman.mp4"));
-
- //Four Digits seasons
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2009\2009x02 blah.avi"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2009\S2009x02 blah.avi"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2009\S2009E02 blah.avi"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2009\S2009xE02 blah.avi"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2009\seriesname 2009x02 blah.avi"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2009\seriesname S2009x02 blah.avi"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2009\seriesname S2009E02 blah.avi"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2009\seriesname S2009xE02 blah.avi"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2009\Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.ext"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2009\2009x03 - 2009x04 - 2009x15 - Ep Name.ext"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2009\2009x03-04-15 - Ep Name.ext"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2009\Elementary - 2009x03-04-15 - Ep Name.ext"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2009\2009x03-E15 - Ep Name.ext"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2009\Elementary - 2009x03-E15 - Ep Name.ext"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2009\2009x03 - x04 - x15 - Ep Name.ext"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2009\Elementary - 2009x03 - x04 - x15 - Ep Name.ext"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2009\2009x03x04x15 - Ep Name.ext"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2009\Elementary - 2009x03x04x15 - Ep Name.ext"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2009\Elementary - S2009E23-E24-E26 - The Woman.mp4"));
- Assert.AreEqual(2009, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 2009\S2009E23-E24-E26 - The Woman.mp4"));
- Assert.AreEqual(25, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"Season 25\The Simpsons.S25E09.Steal this episode.mp4"));
- Assert.AreEqual(25, SeriesResolver.GetSeasonNumberFromEpisodeFile(@"The Simpsons\The Simpsons.S25E09.Steal this episode.mp4"));
- }
- }
-}
diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs
index eb44f6d35..6e3439079 100644
--- a/MediaBrowser.WebDashboard/Api/DashboardService.cs
+++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs
@@ -8,6 +8,7 @@ using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization;
using ServiceStack;
using ServiceStack.Web;
diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs
index b3a1bf84a..2f9f3f5f5 100644
--- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs
+++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs
@@ -197,6 +197,8 @@ namespace MediaBrowser.WebDashboard.Api
{
"thirdparty/jquerymobile-1.4.5/jquery.mobile-1.4.5.min.css",
"thirdparty/swipebox-master/css/swipebox.min.css" + versionString,
+ "thirdparty/fontawesome/css/font-awesome.min.css" + versionString,
+ "thirdparty/jstree3.0.8/themes/default/style.min.css",
"css/all.css" + versionString
};
@@ -219,7 +221,7 @@ namespace MediaBrowser.WebDashboard.Api
var files = new[]
{
"scripts/all.js" + versionString,
- "thirdparty/jstree1.0/jquery.jstree.min.js",
+ "thirdparty/jstree3.0.8/jstree.min.js",
"thirdparty/swipebox-master/js/jquery.swipebox.min.js" + versionString
};
@@ -332,6 +334,7 @@ namespace MediaBrowser.WebDashboard.Api
"chromecast.js",
"backdrops.js",
"sync.js",
+ "syncjob.js",
"playlistmanager.js",
"mediaplayer.js",
diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
index d8e6561ab..1343b1e69 100644
--- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
+++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
@@ -93,6 +93,12 @@
<Content Include="dashboard-ui\forgotpasswordpin.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\mysync.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\mysyncjob.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\forgotpassword.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -102,6 +108,9 @@
<Content Include="dashboard-ui\scripts\selectserver.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\syncjob.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\syncsettings.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -114,6 +123,9 @@
<Content Include="dashboard-ui\selectserver.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\syncjob.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\syncsettings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -210,27 +222,15 @@
<Content Include="dashboard-ui\css\images\favicon.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\headersearch.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\css\images\icons\ellipsis-v.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\css\images\icons\remote.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\items\folders\edit.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\css\images\items\folders\home.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\items\folders\report.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\items\folders\settings.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\css\images\icons\audiocd.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -594,9 +594,6 @@
<Content Include="dashboard-ui\css\images\rotten.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\currentuserdefaultwhite.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\css\images\items\detail\person.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -627,33 +624,9 @@
<Content Include="dashboard-ui\css\images\userdata\administrator.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\userdata\heart_off.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\userdata\heart_on.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\css\images\userdata\password.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\userdata\thumbs_down_off.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\userdata\thumbs_down_on.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\userdata\thumbs_up_off.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\userdata\thumbs_up_on.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\userdata\checkedoff.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\userdata\checkedon.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\css\librarybrowser.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -963,625 +936,19 @@
<Content Include="dashboard-ui\thirdparty\cast_sender.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jquery-2.1.1.min.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquery.unveil-custom.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\ajax-loader.gif">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\action-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\action-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\alert-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\alert-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\arrow-d-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\arrow-d-l-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\arrow-d-l-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\arrow-d-r-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\arrow-d-r-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\arrow-d-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\arrow-l-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\arrow-l-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\arrow-r-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\arrow-r-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\arrow-u-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\arrow-u-l-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\arrow-u-l-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\arrow-u-r-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\arrow-u-r-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\arrow-u-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\audio-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\audio-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\back-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\back-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\bars-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\bars-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\bullets-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\bullets-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\calendar-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\calendar-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\camera-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\camera-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\carat-d-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\carat-d-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\carat-l-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\carat-l-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\carat-r-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\carat-r-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\carat-u-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\carat-u-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\check-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\check-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\clock-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\clock-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\cloud-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\cloud-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\comment-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\comment-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\delete-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\delete-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\edit-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\edit-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\eye-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\eye-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\forbidden-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\forbidden-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\forward-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\forward-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\gear-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\gear-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\grid-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\grid-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\heart-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\heart-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\home-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\home-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\info-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\info-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\location-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\location-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\lock-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\lock-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\mail-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\mail-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\minus-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\minus-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\navigation-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\navigation-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\phone-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\phone-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\plus-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\plus-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\power-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\power-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\recycle-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\recycle-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\refresh-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\refresh-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\search-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\search-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\shop-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\shop-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\star-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\star-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\tag-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\tag-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\user-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\user-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\video-black.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-png\video-white.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\action-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\action-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\alert-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\alert-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\arrow-d-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\arrow-d-l-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\arrow-d-l-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\arrow-d-r-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\arrow-d-r-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\arrow-d-white.svg">
+ <Content Include="dashboard-ui\thirdparty\fontawesome\css\font-awesome.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\arrow-l-black.svg">
+ <Content Include="dashboard-ui\thirdparty\fontawesome\css\font-awesome.min.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\arrow-l-white.svg">
+ <Content Include="dashboard-ui\thirdparty\fontawesome\fonts\fontawesome-webfont.svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\arrow-r-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\arrow-r-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\arrow-u-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\arrow-u-l-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\arrow-u-l-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\arrow-u-r-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\arrow-u-r-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\arrow-u-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\audio-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\audio-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\back-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\back-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\bars-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\bars-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\bullets-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\bullets-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\calendar-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\calendar-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\camera-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\camera-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\carat-d-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\carat-d-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\carat-l-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\carat-l-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\carat-r-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\carat-r-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\carat-u-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\carat-u-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\check-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\check-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\clock-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\clock-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\cloud-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\cloud-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\comment-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\comment-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\delete-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\delete-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\edit-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\edit-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\eye-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\eye-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\forbidden-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\forbidden-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\forward-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\forward-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\gear-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\gear-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\grid-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\grid-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\heart-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\heart-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\home-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\home-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\info-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\info-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\location-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\location-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\lock-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\lock-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\mail-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\mail-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\minus-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\minus-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\navigation-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\navigation-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\phone-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\phone-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\plus-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\plus-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\power-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\power-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\recycle-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\recycle-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\refresh-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\refresh-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\search-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\search-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\shop-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\shop-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\star-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\star-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\tag-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\tag-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\user-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\user-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\video-black.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\images\icons-svg\video-white.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\jquery.mobile-1.4.3.min.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\jquery.mobile-1.4.3.min.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\jquery.mobile-1.4.4.min.css">
+ <Content Include="dashboard-ui\thirdparty\jquery-2.1.1.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\jquery.mobile-1.4.4.min.js">
+ <Content Include="dashboard-ui\thirdparty\jquery.unveil-custom.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\ajax-loader.gif">
@@ -2193,15 +1560,48 @@
<Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jquery.mobile-1.4.5.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\jquery.jstree.min.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\livetvsuggested.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\thirdparty\apiclient\md5.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\thirdparty\jstree3.0.8\jstree.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jstree3.0.8\jstree.min.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jstree3.0.8\themes\default-dark\32px.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jstree3.0.8\themes\default-dark\40px.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jstree3.0.8\themes\default-dark\style.css">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jstree3.0.8\themes\default-dark\style.min.css">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jstree3.0.8\themes\default-dark\throbber.gif">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jstree3.0.8\themes\default\32px.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jstree3.0.8\themes\default\40px.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jstree3.0.8\themes\default\style.css">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jstree3.0.8\themes\default\style.min.css">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jstree3.0.8\themes\default\throbber.gif">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\thirdparty\swipebox-master\css\swipebox.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -2438,78 +1838,6 @@
<Content Include="dashboard-ui\scripts\tvstudios.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\jquery.jstree.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\apple\bg.jpg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\apple\d.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\apple\dot_for_ie.gif">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\apple\style.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\apple\throbber.gif">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\classic\d.gif">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\classic\d.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\classic\dot_for_ie.gif">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\classic\style.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\classic\throbber.gif">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\default-rtl\d.gif">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\default-rtl\d.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\default-rtl\dots.gif">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\default-rtl\style.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\default-rtl\throbber.gif">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\default\d.gif">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\default\d.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\default\style.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\default\throbber.gif">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\mb3\d.gif">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\mb3\d.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\mb3\style.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\mb3\throbber.gif">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\tvgenres.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -2917,10 +2245,16 @@
<None Include="dashboard-ui\css\fonts\RobotoThin.woff">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
- <None Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\jquery.mobile-1.4.3.min.map">
+ <None Include="dashboard-ui\thirdparty\fontawesome\fonts\fontawesome-webfont.eot">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
+ <None Include="dashboard-ui\thirdparty\fontawesome\fonts\fontawesome-webfont.ttf">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
+ <None Include="dashboard-ui\thirdparty\fontawesome\fonts\fontawesome-webfont.woff">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
- <None Include="dashboard-ui\thirdparty\jquerymobile-1.4.4\jquery.mobile-1.4.4.min.map">
+ <None Include="dashboard-ui\thirdparty\fontawesome\fonts\FontAwesome.otf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jquery.mobile-1.4.5.min.map">
diff --git a/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs
index 583255891..9bd9ac65e 100644
--- a/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs
@@ -36,7 +36,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
return false;
}
- return item is MusicAlbum && updateType >= ItemUpdateType.ImageUpdate;
+ return item is MusicAlbum && updateType >= MinimumUpdateType;
}
protected override void WriteCustomElements(IHasMetadata item, XmlWriter writer)
diff --git a/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs
index 1ef0304fe..c592ec4b7 100644
--- a/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs
@@ -36,7 +36,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
return false;
}
- return item is MusicArtist && updateType >= ItemUpdateType.ImageUpdate;
+ return item is MusicArtist && updateType >= MinimumUpdateType;
}
protected override void WriteCustomElements(IHasMetadata item, XmlWriter writer)
diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
index 86e92530f..da5126676 100644
--- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
@@ -119,6 +119,19 @@ namespace MediaBrowser.XbmcMetadata.Savers
protected IUserDataManager UserDataManager { get; private set; }
protected ILogger Logger { get; private set; }
+ protected ItemUpdateType MinimumUpdateType
+ {
+ get
+ {
+ if (ConfigurationManager.GetNfoConfiguration().SaveImagePathsInNfo)
+ {
+ return ItemUpdateType.ImageUpdate;
+ }
+
+ return ItemUpdateType.MetadataDownload;
+ }
+ }
+
public string Name
{
get
@@ -257,6 +270,10 @@ namespace MediaBrowser.XbmcMetadata.Savers
{
}
+ catch (DirectoryNotFoundException)
+ {
+
+ }
writer.WriteEndElement();
diff --git a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs
index 090d41015..6fd396e40 100644
--- a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs
@@ -35,7 +35,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
return false;
}
- return item is Episode && updateType >= ItemUpdateType.ImageUpdate;
+ return item is Episode && updateType >= MinimumUpdateType;
}
protected override void WriteCustomElements(IHasMetadata item, XmlWriter writer)
diff --git a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs
index bfb2b916e..d049843f3 100644
--- a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs
@@ -55,7 +55,15 @@ namespace MediaBrowser.XbmcMetadata.Savers
// Check parent for null to avoid running this against things like video backdrops
if (video != null && !(item is Episode) && !video.IsOwnedItem)
{
- return updateType >= ItemUpdateType.ImageUpdate;
+ // If it's a plain video, skip if content type is unset (unless editing)
+ if (video.GetType() == typeof (Video))
+ {
+ if (updateType < ItemUpdateType.MetadataEdit && string.IsNullOrEmpty(LibraryManager.GetContentType(video)))
+ {
+ return false;
+ }
+ }
+ return updateType >= MinimumUpdateType;
}
return false;
@@ -95,8 +103,6 @@ namespace MediaBrowser.XbmcMetadata.Savers
}
}
- private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
protected override List<string> GetTagsUsed()
{
var list = new List<string>
diff --git a/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs
index 99a53fd9e..49ace7702 100644
--- a/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs
@@ -39,7 +39,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
return false;
}
- return updateType >= ItemUpdateType.ImageUpdate || (updateType >= ItemUpdateType.MetadataImport && File.Exists(GetSavePath(item)));
+ return updateType >= MinimumUpdateType || (updateType >= ItemUpdateType.MetadataImport && File.Exists(GetSavePath(item)));
}
protected override void WriteCustomElements(IHasMetadata item, XmlWriter writer)
diff --git a/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs
index 0498919ae..099e8dbd7 100644
--- a/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs
@@ -35,7 +35,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
return false;
}
- return item is Series && updateType >= ItemUpdateType.ImageUpdate;
+ return item is Series && updateType >= MinimumUpdateType;
}
protected override void WriteCustomElements(IHasMetadata item, XmlWriter writer)
diff --git a/MediaBrowser.sln b/MediaBrowser.sln
index 22c2fdc24..c081917fb 100644
--- a/MediaBrowser.sln
+++ b/MediaBrowser.sln
@@ -7,7 +7,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{F0E0E6
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8C5D6ABC-D277-407B-8061-3AA04251D539}"
ProjectSection(SolutionItems) = preProject
+ Performance1.psess = Performance1.psess
Performance19.psess = Performance19.psess
+ Performance2.psess = Performance2.psess
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget (2)", ".nuget (2)", "{E60FB157-87E2-4A41-8B04-27EA49B63B4D}"
@@ -516,4 +518,7 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(Performance) = preSolution
+ HasPerformanceSessions = true
+ EndGlobalSection
EndGlobal
diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec
index 3ecc9f9a9..d7d4a2120 100644
--- a/Nuget/MediaBrowser.Common.Internal.nuspec
+++ b/Nuget/MediaBrowser.Common.Internal.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common.Internal</id>
- <version>3.0.521</version>
+ <version>3.0.540</version>
<title>MediaBrowser.Common.Internal</title>
<authors>Luke</authors>
<owners>ebr,Luke,scottisafool</owners>
@@ -12,10 +12,9 @@
<description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
<copyright>Copyright © Media Browser 2013</copyright>
<dependencies>
- <dependency id="MediaBrowser.Common" version="3.0.521" />
+ <dependency id="MediaBrowser.Common" version="3.0.540" />
<dependency id="NLog" version="3.1.0.0" />
<dependency id="SimpleInjector" version="2.6.1" />
- <dependency id="sharpcompress" version="0.10.2" />
</dependencies>
</metadata>
<files>
diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec
index da3e29b2a..c8f2f24cb 100644
--- a/Nuget/MediaBrowser.Common.nuspec
+++ b/Nuget/MediaBrowser.Common.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common</id>
- <version>3.0.521</version>
+ <version>3.0.540</version>
<title>MediaBrowser.Common</title>
<authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners>
diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec
index b0fe2b2c4..c79313850 100644
--- a/Nuget/MediaBrowser.Model.Signed.nuspec
+++ b/Nuget/MediaBrowser.Model.Signed.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Model.Signed</id>
- <version>3.0.521</version>
+ <version>3.0.540</version>
<title>MediaBrowser.Model - Signed Edition</title>
<authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners>
diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec
index 57d224fd4..807e49e7f 100644
--- a/Nuget/MediaBrowser.Server.Core.nuspec
+++ b/Nuget/MediaBrowser.Server.Core.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>MediaBrowser.Server.Core</id>
- <version>3.0.521</version>
+ <version>3.0.540</version>
<title>Media Browser.Server.Core</title>
<authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
<description>Contains core components required to build plugins for Media Browser Server.</description>
<copyright>Copyright © Media Browser 2013</copyright>
<dependencies>
- <dependency id="MediaBrowser.Common" version="3.0.521" />
+ <dependency id="MediaBrowser.Common" version="3.0.540" />
</dependencies>
</metadata>
<files>
diff --git a/SharedVersion.cs b/SharedVersion.cs
index d323dc7bc..eebcb54b1 100644
--- a/SharedVersion.cs
+++ b/SharedVersion.cs
@@ -1,4 +1,4 @@
using System.Reflection;
//[assembly: AssemblyVersion("3.0.*")]
-[assembly: AssemblyVersion("3.0.5464.40000")]
+[assembly: AssemblyVersion("3.0.5482.0")]