diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index d386373d4b..95f7ef6944 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -185,7 +185,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/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 12ccfb6b1d..1a8c1d849e 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -302,6 +302,21 @@ namespace MediaBrowser.Api.Playback } } + protected string H264Encoder + { + get + { + var lib = ServerConfigurationManager.Configuration.H264Encoder; + + if (!string.IsNullOrWhiteSpace(lib)) + { + return lib; + } + + return "libx264"; + } + } + /// /// Gets the video bitrate to specify on the command line /// @@ -318,7 +333,7 @@ namespace MediaBrowser.Api.Playback var qualitySetting = GetQualitySetting(); - if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(videoCodec, H264Encoder, StringComparison.OrdinalIgnoreCase)) { switch (qualitySetting) { @@ -761,7 +776,7 @@ namespace MediaBrowser.Api.Playback { if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase)) { - return "libx264"; + return H264Encoder; } if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase)) { @@ -1562,9 +1577,6 @@ namespace MediaBrowser.Api.Playback mediaStreams = new List(); 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; @@ -1696,6 +1708,13 @@ 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); } diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index c963636fdf..c2a9b963c5 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; @@ -119,11 +119,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()); } var audioBitrate = state.OutputAudioBitrate ?? 0; @@ -144,6 +140,22 @@ namespace MediaBrowser.Api.Playback.Hls return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary()); } + 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(); diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index fe3bd12fb1..7903724e8d 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -651,7 +651,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; + var args = "-codec:v:0 " + codec + " " + GetVideoQualityParam(state, H264Encoder, true) + keyFrameArg; // Add resolution params, if specified if (!hasGraphicalSubs) diff --git a/MediaBrowser.Api/Playback/Hls/MpegDashService.cs b/MediaBrowser.Api/Playback/Hls/MpegDashService.cs index 260a4c467e..ca46df05d0 100644 --- a/MediaBrowser.Api/Playback/Hls/MpegDashService.cs +++ b/MediaBrowser.Api/Playback/Hls/MpegDashService.cs @@ -594,7 +594,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; + var args = "-codec:v:0 " + codec + " " + GetVideoQualityParam(state, H264Encoder, true) + keyFrameArg; args += " -r 24 -g 24"; diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 06fa4065c2..14f7175a9c 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -9,8 +9,6 @@ using MediaBrowser.Model.IO; using ServiceStack; using System; using System.IO; -using System.Linq; -using System.Threading.Tasks; namespace MediaBrowser.Api.Playback.Hls { @@ -147,7 +145,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; + var args = "-codec:v:0 " + codec + " " + GetVideoQualityParam(state, H264Encoder, true) + keyFrameArg; // Add resolution params, if specified if (!hasGraphicalSubs) diff --git a/MediaBrowser.Api/Sync/SyncService.cs b/MediaBrowser.Api/Sync/SyncService.cs index cefb0e46e8..26e4a2669a 100644 --- a/MediaBrowser.Api/Sync/SyncService.cs +++ b/MediaBrowser.Api/Sync/SyncService.cs @@ -55,8 +55,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.")] @@ -155,19 +161,26 @@ 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 - { - Fields = new List + if (request.Category.HasValue) + { + result.Options = SyncHelper.GetSyncOptions(request.Category.Value); + } + else + { + var dtos = request.ItemIds.Split(',') + .Select(_libraryManager.GetItemById) + .Where(i => i != null) + .Select(i => _dtoService.GetBaseItemDto(i, new DtoOptions + { + Fields = new List { ItemFields.SyncInfo } - })) - .ToList(); + })) + .ToList(); - result.Options = SyncHelper.GetSyncOptions(dtos); + result.Options = SyncHelper.GetSyncOptions(dtos); + } return ToOptimizedResult(result); } diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 4b720c775c..9c8216a03c 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -5,6 +5,7 @@ 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 +52,7 @@ namespace MediaBrowser.Api /// /// The id. [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid Id { get; set; } + public string Id { get; set; } } /// @@ -66,7 +67,7 @@ namespace MediaBrowser.Api /// /// The id. [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public Guid Id { get; set; } + public string Id { get; set; } } /// @@ -80,7 +81,7 @@ namespace MediaBrowser.Api /// /// The id. [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid Id { get; set; } + public string Id { get; set; } /// /// Gets or sets the password. @@ -125,7 +126,7 @@ namespace MediaBrowser.Api /// Gets or sets the id. /// /// The id. - public Guid Id { get; set; } + public string Id { get; set; } /// /// Gets or sets the password. @@ -155,6 +156,28 @@ namespace MediaBrowser.Api { } + /// + /// Class UpdateUser + /// + [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; } + } + + /// + /// Class UpdateUser + /// + [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; } + } + /// /// Class CreateUser /// @@ -196,12 +219,6 @@ namespace MediaBrowser.Api public IAuthorizationContext AuthorizationContext { get; set; } - /// - /// Initializes a new instance of the class. - /// - /// The user manager. - /// The dto service. - /// The session mananger. public UserService(IUserManager userManager, IDtoService dtoService, ISessionManager sessionMananger, IServerConfigurationManager config, INetworkManager networkManager) { _userManager = userManager; @@ -495,5 +512,17 @@ namespace MediaBrowser.Api { return _userManager.RedeemPasswordResetPin(request.Pin); } + + public void Post(UpdateUserConfiguration request) + { + var user = _userManager.GetUserById(request.Id); + user.UpdateConfiguration(request); + } + + public void Post(UpdateUserPolicy request) + { + var task = _userManager.UpdateUserPolicy(request.Id, request); + Task.WaitAll(task); + } } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index ed950b1c5e..1d57c46e69 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -45,6 +45,8 @@ namespace MediaBrowser.Controller.Entities /// public static readonly string[] SupportedImageExtensions = { ".png", ".jpg", ".jpeg", ".tbn" }; + public static readonly List SupportedImageExtensionsList = SupportedImageExtensions.ToList(); + /// /// The trailer folder name /// diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index e749d89e4f..30bf0fefc5 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -153,7 +153,14 @@ namespace MediaBrowser.Controller.Entities.Movies public MovieInfo GetLookupInfo() { - return GetItemLookupInfo(); + var info = GetItemLookupInfo(); + + if (!IsInMixedFolder) + { + info.Name = System.IO.Path.GetFileName(ContainingFolderPath); + } + + return info; } public override bool BeforeMetadataRefresh() diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index cc0fc6812c..e270bbdf30 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using System; @@ -301,51 +300,9 @@ namespace MediaBrowser.Controller.Entities.TV { var hasChanges = base.BeforeMetadataRefresh(); - var locationType = LocationType; - if (locationType == LocationType.FileSystem || locationType == LocationType.Offline) + if (LibraryManager.FillMissingEpisodeNumbersFromPath(this)) { - 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) - { - 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/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 33dea4dca6..6d8f89226a 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; @@ -340,26 +341,11 @@ namespace MediaBrowser.Controller.Library int? GetSeasonNumberFromPath(string path); /// - /// Gets the season number from episode file. + /// Fills the missing episode numbers from path. /// - /// The path. - /// System.Nullable<System.Int32>. - int? GetSeasonNumberFromEpisodeFile(string path); - - /// - /// Gets the ending episode number from file. - /// - /// The path. - /// System.Nullable<System.Int32>. - int? GetEndingEpisodeNumberFromFile(string path); - - /// - /// Gets the episode number from file. - /// - /// The path. - /// if set to true [consider seasonless]. - /// System.Nullable<System.Int32>. - int? GetEpisodeNumberFromFile(string path, bool considerSeasonless); + /// The episode. + /// true if XXXX, false otherwise. + bool FillMissingEpisodeNumbersFromPath(Episode episode); /// /// Parses the name. diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index 044d29a1b8..9b1cce915b 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -76,11 +76,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 GetImages(IHasImages item, IDirectoryService directoryService) @@ -109,6 +112,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(); @@ -402,13 +406,7 @@ namespace MediaBrowser.LocalMetadata.Images private FileSystemInfo GetImage(IEnumerable 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.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index 9521f85381..dde6ca0b12 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -185,6 +185,14 @@ namespace MediaBrowser.Model.ApiClient /// url Task GetImageStreamAsync(string url, CancellationToken cancellationToken = default(CancellationToken)); + /// + /// Updates the user configuration. + /// + /// The user identifier. + /// The configuration. + /// Task. + Task UpdateUserConfiguration(string userId, UserConfiguration configuration); + /// /// Gets a BaseItem /// diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index b9eaf70010..59dd04f338 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -144,12 +144,6 @@ namespace MediaBrowser.Model.Configuration /// The image saving convention. public ImageSavingConvention ImageSavingConvention { get; set; } - /// - /// Gets or sets a value indicating whether [enable people prefix sub folders]. - /// - /// true if [enable people prefix sub folders]; otherwise, false. - public bool EnablePeoplePrefixSubFolders { get; set; } - /// /// Gets or sets the encoding quality. /// @@ -179,8 +173,7 @@ namespace MediaBrowser.Model.Configuration public string[] InsecureApps7 { get; set; } public bool SaveMetadataHidden { get; set; } - - public bool PlaylistImagesDeleted { get; set; } + public string H264Encoder { get; set; } /// /// Initializes a new instance of the class. @@ -195,7 +188,6 @@ namespace MediaBrowser.Model.Configuration EnableDashboardResponseCaching = true; EnableAutomaticRestart = true; - EnablePeoplePrefixSubFolders = true; EnableUPnP = true; DownMixAudioBoost = 2; @@ -225,6 +217,7 @@ namespace MediaBrowser.Model.Configuration EnableRealtimeMonitor = true; UICulture = "en-us"; + H264Encoder = "libx264"; PeopleMetadataOptions = new PeopleMetadataOptions(); diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 71cefa0766..87fdce7996 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -549,7 +549,13 @@ namespace MediaBrowser.Model.Dto /// Gets or sets a value indicating whether [supports playlists]. /// /// true if [supports playlists]; otherwise, false. - public bool SupportsPlaylists { get; set; } + public bool SupportsPlaylists + { + get + { + return RunTimeTicks.HasValue || IsFolder || IsGenre || IsMusicGenre || IsArtist; + } + } /// /// Determines whether the specified type is type. diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index 19e30cd8a7..875894c077 100644 --- a/MediaBrowser.Model/Querying/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -6,6 +6,11 @@ namespace MediaBrowser.Model.Querying /// public enum ItemFields { + /// + /// The air time + /// + AirTime, + /// /// The alternate episode numbers /// @@ -151,6 +156,11 @@ namespace MediaBrowser.Model.Querying /// Revenue, + /// + /// The season name + /// + SeasonName, + /// /// The short overview /// @@ -181,6 +191,11 @@ namespace MediaBrowser.Model.Querying /// SortName, + /// + /// The special episode numbers + /// + SpecialEpisodeNumbers, + /// /// The studios of the item /// diff --git a/MediaBrowser.Model/Session/TranscodingInfo.cs b/MediaBrowser.Model/Session/TranscodingInfo.cs index b3ab32a448..e646d80d35 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/SyncItem.cs b/MediaBrowser.Model/Sync/SyncItem.cs new file mode 100644 index 0000000000..d50ae98c99 --- /dev/null +++ b/MediaBrowser.Model/Sync/SyncItem.cs @@ -0,0 +1,9 @@ +using MediaBrowser.Model.Dto; + +namespace MediaBrowser.Model.Sync +{ + public class SyncItem + { + public BaseItemDto Item { get; set; } + } +} diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelPostScanTask.cs b/MediaBrowser.Server.Implementations/Channels/ChannelPostScanTask.cs index 72c524ec56..d266cca6c0 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(); + if (maxRefreshLevel > 0) + { + var innerProgress = new ActionableProgress(); - var startingNumberComplete = numComplete; - innerProgress.RegisterAction(p => - { - double innerPercent = startingNumberComplete; - innerPercent += (p / 100); - innerPercent /= numItems; - progress.Report(innerPercent * 100); - }); + 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); + 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/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 1020a43735..3579f443fa 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -121,8 +121,6 @@ namespace MediaBrowser.Server.Implementations.Dto ServerId = _appHost.SystemId }; - dto.SupportsPlaylists = item.SupportsAddingToPlaylist; - if (fields.Contains(ItemFields.People)) { AttachPeople(dto, item); @@ -1132,15 +1130,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 +1185,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/FileOrganization/EpisodeFileOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs index d4625d4028..3b5e34520f 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) diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 4cb39778c5..d61a00ac39 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; @@ -862,7 +862,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); } @@ -1708,22 +1708,69 @@ 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 locationType = episode.LocationType; + + var fileType = /*args.IsDirectory ? FileInfoType.Directory :*/ FileInfoType.File; + var episodeInfo = locationType == LocationType.FileSystem || locationType == LocationType.Offline ? + resolver.Resolve(episode.Path, fileType) : + new Naming.TV.EpisodeInfo(); - public int? GetEpisodeNumberFromFile(string path, bool considerSeasonless) - { - return SeriesResolver.GetEpisodeNumberFromFile(path, considerSeasonless); + if (episodeInfo == null) + { + episodeInfo = new Naming.TV.EpisodeInfo(); + } + + var changed = false; + + 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/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 276b99d3a2..0e5ed18251 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -82,8 +82,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies return ResolveVideos(parent, files, 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 ResolveVideos(parent, files, directoryService, collectionType); } @@ -117,7 +116,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies FullName = i.FullName, Type = FileInfoType.File - }).ToList()).ToList(); + }).ToList(), false).ToList(); var result = new MultiItemResolverResult { @@ -168,12 +167,12 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies { if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType, false); + return FindMovie(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType); } if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie