diff options
Diffstat (limited to 'MediaBrowser.Controller')
51 files changed, 1167 insertions, 425 deletions
diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index 1a8583489..2ecf3ec9a 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -47,7 +47,7 @@ namespace MediaBrowser.Controller.Drawing /// <param name="item">The item.</param> /// <param name="imageType">Type of the image.</param> /// <returns>IEnumerable{IImageEnhancer}.</returns> - IEnumerable<IImageEnhancer> GetSupportedEnhancers(BaseItem item, ImageType imageType); + IEnumerable<IImageEnhancer> GetSupportedEnhancers(IHasImages item, ImageType imageType); /// <summary> /// Gets the image cache tag. @@ -56,7 +56,7 @@ namespace MediaBrowser.Controller.Drawing /// <param name="imageType">Type of the image.</param> /// <param name="imagePath">The image path.</param> /// <returns>Guid.</returns> - Guid GetImageCacheTag(BaseItem item, ImageType imageType, string imagePath); + Guid GetImageCacheTag(IHasImages item, ImageType imageType, string imagePath); /// <summary> /// Gets the image cache tag. @@ -67,7 +67,7 @@ namespace MediaBrowser.Controller.Drawing /// <param name="dateModified">The date modified.</param> /// <param name="imageEnhancers">The image enhancers.</param> /// <returns>Guid.</returns> - Guid GetImageCacheTag(BaseItem item, ImageType imageType, string originalImagePath, DateTime dateModified, + Guid GetImageCacheTag(IHasImages item, ImageType imageType, string originalImagePath, DateTime dateModified, List<IImageEnhancer> imageEnhancers); /// <summary> @@ -85,6 +85,6 @@ namespace MediaBrowser.Controller.Drawing /// <param name="imageType">Type of the image.</param> /// <param name="imageIndex">Index of the image.</param> /// <returns>Task{System.String}.</returns> - Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex); + Task<string> GetEnhancedImage(IHasImages item, ImageType imageType, int imageIndex); } } diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs index ce4bf6c32..506d6fd3d 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Controller.Drawing { public class ImageProcessingOptions { - public BaseItem Item { get; set; } + public IHasImages Item { get; set; } public ImageType ImageType { get; set; } diff --git a/MediaBrowser.Controller/Entities/AdultVideo.cs b/MediaBrowser.Controller/Entities/AdultVideo.cs index 9bb0f8355..f81cfa1f6 100644 --- a/MediaBrowser.Controller/Entities/AdultVideo.cs +++ b/MediaBrowser.Controller/Entities/AdultVideo.cs @@ -1,7 +1,14 @@ namespace MediaBrowser.Controller.Entities { - public class AdultVideo : Video + public class AdultVideo : Video, IHasPreferredMetadataLanguage { + public string PreferredMetadataLanguage { get; set; } + + /// <summary> + /// Gets or sets the preferred metadata country code. + /// </summary> + /// <value>The preferred metadata country code.</value> + public string PreferredMetadataCountryCode { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 63c907c1f..028fc964d 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -1,4 +1,5 @@ -using System; +using MediaBrowser.Model.Configuration; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; @@ -8,7 +9,7 @@ namespace MediaBrowser.Controller.Entities.Audio /// <summary> /// Class Audio /// </summary> - public class Audio : BaseItem, IHasMediaStreams, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLanguage + public class Audio : BaseItem, IHasMediaStreams, IHasAlbumArtist, IHasArtist, IHasMusicGenres { public Audio() { @@ -16,12 +17,6 @@ namespace MediaBrowser.Controller.Entities.Audio } /// <summary> - /// Gets or sets the language. - /// </summary> - /// <value>The language.</value> - public string Language { get; set; } - - /// <summary> /// Gets or sets a value indicating whether this instance has embedded image. /// </summary> /// <value><c>true</c> if this instance has embedded image; otherwise, <c>false</c>.</value> @@ -131,5 +126,10 @@ namespace MediaBrowser.Controller.Entities.Audio return base.GetUserDataKey(); } + + protected override bool GetBlockUnratedValue(UserConfiguration config) + { + return config.BlockUnratedMusic; + } } } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index 3facccec1..203e6dc43 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; using System.Linq; @@ -109,6 +110,11 @@ namespace MediaBrowser.Controller.Entities.Audio return base.GetUserDataKey(); } + + protected override bool GetBlockUnratedValue(UserConfiguration config) + { + return config.BlockUnratedMusic; + } } public class MusicAlbumDisc : Folder diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 3be555f49..860d34fd8 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; @@ -96,7 +97,7 @@ namespace MediaBrowser.Controller.Entities.Audio /// </summary> /// <param name="item">The item.</param> /// <returns>System.String.</returns> - public static string GetUserDataKey(BaseItem item) + private static string GetUserDataKey(MusicArtist item) { var id = item.GetProviderId(MetadataProviders.Musicbrainz); @@ -107,5 +108,10 @@ namespace MediaBrowser.Controller.Entities.Audio return "Artist-" + item.Name; } + + protected override bool GetBlockUnratedValue(UserConfiguration config) + { + return config.BlockUnratedMusic; + } } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 541887598..a02369b2c 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -7,6 +7,7 @@ using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; @@ -22,7 +23,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Class BaseItem /// </summary> - public abstract class BaseItem : IHasProviderIds, ILibraryItem + public abstract class BaseItem : IHasProviderIds, ILibraryItem, IHasImages, IHasUserData { protected BaseItem() { @@ -132,8 +133,8 @@ namespace MediaBrowser.Controller.Entities [IgnoreDataMember] public string PrimaryImagePath { - get { return GetImage(ImageType.Primary); } - set { SetImage(ImageType.Primary, value); } + get { return this.GetImagePath(ImageType.Primary); } + set { this.SetImagePath(ImageType.Primary, value); } } /// <summary> @@ -956,6 +957,66 @@ namespace MediaBrowser.Controller.Entities } /// <summary> + /// Gets the preferred metadata language. + /// </summary> + /// <returns>System.String.</returns> + public string GetPreferredMetadataLanguage() + { + string lang = null; + + var hasLang = this as IHasPreferredMetadataLanguage; + + if (hasLang != null) + { + lang = hasLang.PreferredMetadataLanguage; + } + + if (string.IsNullOrEmpty(lang)) + { + lang = Parents.OfType<IHasPreferredMetadataLanguage>() + .Select(i => i.PreferredMetadataLanguage) + .FirstOrDefault(i => !string.IsNullOrEmpty(i)); + } + + if (string.IsNullOrEmpty(lang)) + { + lang = ConfigurationManager.Configuration.PreferredMetadataLanguage; + } + + return lang; + } + + /// <summary> + /// Gets the preferred metadata language. + /// </summary> + /// <returns>System.String.</returns> + public string GetPreferredMetadataCountryCode() + { + string lang = null; + + var hasLang = this as IHasPreferredMetadataLanguage; + + if (hasLang != null) + { + lang = hasLang.PreferredMetadataCountryCode; + } + + if (string.IsNullOrEmpty(lang)) + { + lang = Parents.OfType<IHasPreferredMetadataLanguage>() + .Select(i => i.PreferredMetadataCountryCode) + .FirstOrDefault(i => !string.IsNullOrEmpty(i)); + } + + if (string.IsNullOrEmpty(lang)) + { + lang = ConfigurationManager.Configuration.MetadataCountryCode; + } + + return lang; + } + + /// <summary> /// Determines if a given user has access to this item /// </summary> /// <param name="user">The user.</param> @@ -985,7 +1046,7 @@ namespace MediaBrowser.Controller.Entities if (string.IsNullOrEmpty(rating)) { - return !user.Configuration.BlockNotRated; + return !GetBlockUnratedValue(user.Configuration); } var value = localizationManager.GetRatingLevel(rating); @@ -1000,6 +1061,16 @@ namespace MediaBrowser.Controller.Entities } /// <summary> + /// Gets the block unrated value. + /// </summary> + /// <param name="config">The configuration.</param> + /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> + protected virtual bool GetBlockUnratedValue(UserConfiguration config) + { + return config.BlockNotRated; + } + + /// <summary> /// Determines if this folder should be visible to a given user. /// Default is just parental allowed. Can be overridden for more functionality. /// </summary> @@ -1310,31 +1381,10 @@ namespace MediaBrowser.Controller.Entities /// Gets an image /// </summary> /// <param name="type">The type.</param> - /// <returns>System.String.</returns> - /// <exception cref="System.ArgumentException">Backdrops should be accessed using Item.Backdrops</exception> - public string GetImage(ImageType type) - { - if (type == ImageType.Backdrop) - { - throw new ArgumentException("Backdrops should be accessed using Item.Backdrops"); - } - if (type == ImageType.Screenshot) - { - throw new ArgumentException("Screenshots should be accessed using Item.Screenshots"); - } - - string val; - Images.TryGetValue(type, out val); - return val; - } - - /// <summary> - /// Gets an image - /// </summary> - /// <param name="type">The type.</param> + /// <param name="imageIndex">Index of the image.</param> /// <returns><c>true</c> if the specified type has image; otherwise, <c>false</c>.</returns> /// <exception cref="System.ArgumentException">Backdrops should be accessed using Item.Backdrops</exception> - public bool HasImage(ImageType type) + public bool HasImage(ImageType type, int imageIndex) { if (type == ImageType.Backdrop) { @@ -1345,16 +1395,10 @@ namespace MediaBrowser.Controller.Entities throw new ArgumentException("Screenshots should be accessed using Item.Screenshots"); } - return !string.IsNullOrEmpty(GetImage(type)); + return !string.IsNullOrEmpty(this.GetImagePath(type)); } - /// <summary> - /// Sets an image - /// </summary> - /// <param name="type">The type.</param> - /// <param name="path">The path.</param> - /// <exception cref="System.ArgumentException">Backdrops should be accessed using Item.Backdrops</exception> - public void SetImage(ImageType type, string path) + public void SetImagePath(ImageType type, int index, string path) { if (type == ImageType.Backdrop) { @@ -1423,10 +1467,10 @@ namespace MediaBrowser.Controller.Entities else { // Delete the source file - DeleteImagePath(GetImage(type)); + DeleteImagePath(this.GetImagePath(type)); // Remove it from the item - SetImage(type, null); + this.SetImagePath(type, null); } // Refresh metadata @@ -1597,13 +1641,13 @@ namespace MediaBrowser.Controller.Entities { if (imageType == ImageType.Backdrop) { - return BackdropImagePaths[imageIndex]; + return BackdropImagePaths.Count > imageIndex ? BackdropImagePaths[imageIndex] : null; } if (imageType == ImageType.Screenshot) { var hasScreenshots = (IHasScreenshots)this; - return hasScreenshots.ScreenshotImagePaths[imageIndex]; + return hasScreenshots.ScreenshotImagePaths.Count > imageIndex ? hasScreenshots.ScreenshotImagePaths[imageIndex] : null; } if (imageType == ImageType.Chapter) @@ -1611,7 +1655,9 @@ namespace MediaBrowser.Controller.Entities return ItemRepository.GetChapter(Id, imageIndex).ImagePath; } - return GetImage(imageType); + string val; + Images.TryGetValue(imageType, out val); + return val; } /// <summary> @@ -1658,5 +1704,21 @@ namespace MediaBrowser.Controller.Entities { return new[] { Path }; } + + public Task SwapImages(ImageType type, int index1, int index2) + { + if (type != ImageType.Screenshot && type != ImageType.Backdrop) + { + throw new ArgumentException("The change index operation is only applicable to backdrops and screenshots"); + } + + var file1 = GetImagePath(type, index1); + var file2 = GetImagePath(type, index2); + + FileSystem.SwapFiles(file1, file2); + + // Directory watchers should repeat this, but do a quick refresh first + return RefreshMetadata(CancellationToken.None, forceSave: true, allowSlowProviders: false); + } } } diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs index 87b90b824..298941378 100644 --- a/MediaBrowser.Controller/Entities/Book.cs +++ b/MediaBrowser.Controller/Entities/Book.cs @@ -1,8 +1,9 @@ -using System.Collections.Generic; +using MediaBrowser.Model.Configuration; +using System.Collections.Generic; namespace MediaBrowser.Controller.Entities { - public class Book : BaseItem, IHasTags + public class Book : BaseItem, IHasTags, IHasPreferredMetadataLanguage { public override string MediaType { @@ -11,6 +12,7 @@ namespace MediaBrowser.Controller.Entities return Model.Entities.MediaType.Book; } } + /// <summary> /// Gets or sets the tags. /// </summary> @@ -19,6 +21,14 @@ namespace MediaBrowser.Controller.Entities public string SeriesName { get; set; } + public string PreferredMetadataLanguage { get; set; } + + /// <summary> + /// Gets or sets the preferred metadata country code. + /// </summary> + /// <value>The preferred metadata country code.</value> + public string PreferredMetadataCountryCode { get; set; } + /// <summary> /// /// </summary> @@ -42,5 +52,10 @@ namespace MediaBrowser.Controller.Entities { Tags = new List<string>(); } + + protected override bool GetBlockUnratedValue(UserConfiguration config) + { + return config.BlockUnratedBooks; + } } } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 8ef86e54e..fed206a30 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -8,7 +8,6 @@ using MediaBrowser.Model.Entities; using MoreLinq; using System; using System.Collections; -using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; @@ -365,123 +364,125 @@ namespace MediaBrowser.Controller.Entities { var locationType = LocationType; - // Nothing to do here - if (locationType == LocationType.Remote || locationType == LocationType.Virtual) - { - return; - } - cancellationToken.ThrowIfCancellationRequested(); - IEnumerable<BaseItem> nonCachedChildren; + var validChildren = new List<Tuple<BaseItem, bool>>(); - try - { - nonCachedChildren = GetNonCachedChildren(); - } - catch (IOException ex) + if (locationType != LocationType.Remote && locationType != LocationType.Virtual) { - nonCachedChildren = new BaseItem[] { }; + IEnumerable<BaseItem> nonCachedChildren; - Logger.ErrorException("Error getting file system entries for {0}", ex, Path); - } + try + { + nonCachedChildren = GetNonCachedChildren(); + } + catch (IOException ex) + { + nonCachedChildren = new BaseItem[] {}; - if (nonCachedChildren == null) return; //nothing to validate + Logger.ErrorException("Error getting file system entries for {0}", ex, Path); + } - progress.Report(5); + if (nonCachedChildren == null) return; //nothing to validate - //build a dictionary of the current children we have now by Id so we can compare quickly and easily - var currentChildren = ActualChildren.ToDictionary(i => i.Id); + progress.Report(5); - //create a list for our validated children - var validChildren = new List<Tuple<BaseItem, bool>>(); - var newItems = new List<BaseItem>(); + //build a dictionary of the current children we have now by Id so we can compare quickly and easily + var currentChildren = ActualChildren.ToDictionary(i => i.Id); - cancellationToken.ThrowIfCancellationRequested(); + //create a list for our validated children + var newItems = new List<BaseItem>(); - foreach (var child in nonCachedChildren) - { - BaseItem currentChild; + cancellationToken.ThrowIfCancellationRequested(); - if (currentChildren.TryGetValue(child.Id, out currentChild)) + foreach (var child in nonCachedChildren) { - currentChild.ResetResolveArgs(child.ResolveArgs); + BaseItem currentChild; - //existing item - check if it has changed - if (currentChild.HasChanged(child)) + if (currentChildren.TryGetValue(child.Id, out currentChild)) { - var currentChildLocationType = currentChild.LocationType; - if (currentChildLocationType != LocationType.Remote && - currentChildLocationType != LocationType.Virtual) + currentChild.ResetResolveArgs(child.ResolveArgs); + + //existing item - check if it has changed + if (currentChild.HasChanged(child)) + { + var currentChildLocationType = currentChild.LocationType; + if (currentChildLocationType != LocationType.Remote && + currentChildLocationType != LocationType.Virtual) + { + EntityResolutionHelper.EnsureDates(FileSystem, currentChild, child.ResolveArgs, false); + } + + validChildren.Add(new Tuple<BaseItem, bool>(currentChild, true)); + } + else { - EntityResolutionHelper.EnsureDates(FileSystem, currentChild, child.ResolveArgs, false); + validChildren.Add(new Tuple<BaseItem, bool>(currentChild, false)); } - validChildren.Add(new Tuple<BaseItem, bool>(currentChild, true)); + currentChild.IsOffline = false; } else { - validChildren.Add(new Tuple<BaseItem, bool>(currentChild, false)); - } + //brand new item - needs to be added + newItems.Add(child); - currentChild.IsOffline = false; + validChildren.Add(new Tuple<BaseItem, bool>(child, true)); + } } - else + + // If any items were added or removed.... + if (newItems.Count > 0 || currentChildren.Count != validChildren.Count) { - //brand new item - needs to be added - newItems.Add(child); + var newChildren = validChildren.Select(c => c.Item1).ToList(); - validChildren.Add(new Tuple<BaseItem, bool>(child, true)); - } - } + // That's all the new and changed ones - now see if there are any that are missing + var itemsRemoved = currentChildren.Values.Except(newChildren).ToList(); - // If any items were added or removed.... - if (newItems.Count > 0 || currentChildren.Count != validChildren.Count) - { - var newChildren = validChildren.Select(c => c.Item1).ToList(); + var actualRemovals = new List<BaseItem>(); - // That's all the new and changed ones - now see if there are any that are missing - var itemsRemoved = currentChildren.Values.Except(newChildren).ToList(); + foreach (var item in itemsRemoved) + { + if (item.LocationType == LocationType.Virtual || + item.LocationType == LocationType.Remote) + { + // Don't remove these because there's no way to accurately validate them. + validChildren.Add(new Tuple<BaseItem, bool>(item, false)); + } - var actualRemovals = new List<BaseItem>(); + else if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path)) + { + item.IsOffline = true; - foreach (var item in itemsRemoved) - { - if (item.LocationType == LocationType.Virtual || - item.LocationType == LocationType.Remote) - { - // Don't remove these because there's no way to accurately validate them. - continue; + validChildren.Add(new Tuple<BaseItem, bool>(item, false)); + } + else + { + item.IsOffline = false; + actualRemovals.Add(item); + } } - - if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path)) - { - item.IsOffline = true; - validChildren.Add(new Tuple<BaseItem, bool>(item, false)); - } - else + if (actualRemovals.Count > 0) { - item.IsOffline = false; - actualRemovals.Add(item); - } - } + RemoveChildrenInternal(actualRemovals); - if (actualRemovals.Count > 0) - { - RemoveChildrenInternal(actualRemovals); - - foreach (var item in actualRemovals) - { - LibraryManager.ReportItemRemoved(item); + foreach (var item in actualRemovals) + { + LibraryManager.ReportItemRemoved(item); + } } - } - await LibraryManager.CreateItems(newItems, cancellationToken).ConfigureAwait(false); + await LibraryManager.CreateItems(newItems, cancellationToken).ConfigureAwait(false); - AddChildrenInternal(newItems); + AddChildrenInternal(newItems); - await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false); + await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false); + } + } + else + { + validChildren.AddRange(ActualChildren.Select(i => new Tuple<BaseItem, bool>(i, false))); } progress.Report(10); diff --git a/MediaBrowser.Controller/Entities/Game.cs b/MediaBrowser.Controller/Entities/Game.cs index c15a31dd3..da95b7c44 100644 --- a/MediaBrowser.Controller/Entities/Game.cs +++ b/MediaBrowser.Controller/Entities/Game.cs @@ -1,16 +1,25 @@ -using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; namespace MediaBrowser.Controller.Entities { - public class Game : BaseItem, IHasSoundtracks, IHasTrailers, IHasThemeMedia, IHasTags, IHasLanguage, IHasScreenshots + public class Game : BaseItem, IHasSoundtracks, IHasTrailers, IHasThemeMedia, IHasTags, IHasScreenshots, IHasPreferredMetadataLanguage { public List<Guid> SoundtrackIds { get; set; } public List<Guid> ThemeSongIds { get; set; } public List<Guid> ThemeVideoIds { get; set; } + public string PreferredMetadataLanguage { get; set; } + + /// <summary> + /// Gets or sets the preferred metadata country code. + /// </summary> + /// <value>The preferred metadata country code.</value> + public string PreferredMetadataCountryCode { get; set; } + public Game() { MultiPartGameFiles = new List<string>(); @@ -23,12 +32,6 @@ namespace MediaBrowser.Controller.Entities ScreenshotImagePaths = new List<string>(); } - /// <summary> - /// Gets or sets the language. - /// </summary> - /// <value>The language.</value> - public string Language { get; set; } - public List<Guid> LocalTrailerIds { get; set; } /// <summary> @@ -129,5 +132,10 @@ namespace MediaBrowser.Controller.Entities return base.GetDeletePaths(); } + + protected override bool GetBlockUnratedValue(UserConfiguration config) + { + return config.BlockUnratedGames; + } } } diff --git a/MediaBrowser.Controller/Entities/GameSystem.cs b/MediaBrowser.Controller/Entities/GameSystem.cs index 054071b35..63af8082a 100644 --- a/MediaBrowser.Controller/Entities/GameSystem.cs +++ b/MediaBrowser.Controller/Entities/GameSystem.cs @@ -1,4 +1,5 @@ -using System; +using MediaBrowser.Model.Configuration; +using System; namespace MediaBrowser.Controller.Entities { @@ -38,5 +39,11 @@ namespace MediaBrowser.Controller.Entities } return base.GetUserDataKey(); } + + protected override bool GetBlockUnratedValue(UserConfiguration config) + { + // Don't block. Determine by game + return false; + } } } diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index 0fa49639b..53bc64194 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -1,5 +1,4 @@ using MediaBrowser.Model.Dto; -using System; using System.Collections.Generic; using System.Runtime.Serialization; diff --git a/MediaBrowser.Controller/Entities/IHasImages.cs b/MediaBrowser.Controller/Entities/IHasImages.cs new file mode 100644 index 000000000..d800acd9b --- /dev/null +++ b/MediaBrowser.Controller/Entities/IHasImages.cs @@ -0,0 +1,115 @@ +using MediaBrowser.Model.Entities; +using System; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Entities +{ + public interface IHasImages + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + string Name { get; } + + /// <summary> + /// Gets the path. + /// </summary> + /// <value>The path.</value> + string Path { get; } + + /// <summary> + /// Gets the identifier. + /// </summary> + /// <value>The identifier.</value> + Guid Id { get; } + + /// <summary> + /// Gets the image path. + /// </summary> + /// <param name="imageType">Type of the image.</param> + /// <param name="imageIndex">Index of the image.</param> + /// <returns>System.String.</returns> + string GetImagePath(ImageType imageType, int imageIndex); + + /// <summary> + /// Gets the image date modified. + /// </summary> + /// <param name="imagePath">The image path.</param> + /// <returns>DateTime.</returns> + DateTime GetImageDateModified(string imagePath); + + /// <summary> + /// Sets the image. + /// </summary> + /// <param name="type">The type.</param> + /// <param name="index">The index.</param> + /// <param name="path">The path.</param> + void SetImagePath(ImageType type, int index, string path); + + /// <summary> + /// Determines whether the specified type has image. + /// </summary> + /// <param name="type">The type.</param> + /// <param name="imageIndex">Index of the image.</param> + /// <returns><c>true</c> if the specified type has image; otherwise, <c>false</c>.</returns> + bool HasImage(ImageType type, int imageIndex); + + /// <summary> + /// Swaps the images. + /// </summary> + /// <param name="type">The type.</param> + /// <param name="index1">The index1.</param> + /// <param name="index2">The index2.</param> + /// <returns>Task.</returns> + Task SwapImages(ImageType type, int index1, int index2); + + /// <summary> + /// Gets the display type of the media. + /// </summary> + /// <value>The display type of the media.</value> + string DisplayMediaType { get; set; } + + /// <summary> + /// Gets or sets the primary image path. + /// </summary> + /// <value>The primary image path.</value> + string PrimaryImagePath { get; set; } + + /// <summary> + /// Gets the preferred metadata language. + /// </summary> + /// <returns>System.String.</returns> + string GetPreferredMetadataLanguage(); + } + + public static class HasImagesExtensions + { + /// <summary> + /// Gets the image path. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="imageType">Type of the image.</param> + /// <returns>System.String.</returns> + public static string GetImagePath(this IHasImages item, ImageType imageType) + { + return item.GetImagePath(imageType, 0); + } + + public static bool HasImage(this IHasImages item, ImageType imageType) + { + return item.HasImage(imageType, 0); + } + + /// <summary> + /// Sets the image path. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="imageType">Type of the image.</param> + /// <param name="path">The path.</param> + public static void SetImagePath(this IHasImages item, ImageType imageType, string path) + { + item.SetImagePath(imageType, 0, path); + } + } +} diff --git a/MediaBrowser.Controller/Entities/IHasLanguage.cs b/MediaBrowser.Controller/Entities/IHasLanguage.cs deleted file mode 100644 index a1bb80098..000000000 --- a/MediaBrowser.Controller/Entities/IHasLanguage.cs +++ /dev/null @@ -1,15 +0,0 @@ - -namespace MediaBrowser.Controller.Entities -{ - /// <summary> - /// Interface IHasLanguage - /// </summary> - public interface IHasLanguage - { - /// <summary> - /// Gets or sets the language. - /// </summary> - /// <value>The language.</value> - string Language { get; set; } - } -} diff --git a/MediaBrowser.Controller/Entities/IHasPreferredMetadataLanguage.cs b/MediaBrowser.Controller/Entities/IHasPreferredMetadataLanguage.cs new file mode 100644 index 000000000..e3a233e49 --- /dev/null +++ b/MediaBrowser.Controller/Entities/IHasPreferredMetadataLanguage.cs @@ -0,0 +1,21 @@ + +namespace MediaBrowser.Controller.Entities +{ + /// <summary> + /// Interface IHasPreferredMetadataLanguage + /// </summary> + public interface IHasPreferredMetadataLanguage + { + /// <summary> + /// Gets or sets the preferred metadata language. + /// </summary> + /// <value>The preferred metadata language.</value> + string PreferredMetadataLanguage { get; set; } + + /// <summary> + /// Gets or sets the preferred metadata country code. + /// </summary> + /// <value>The preferred metadata country code.</value> + string PreferredMetadataCountryCode { get; set; } + } +} diff --git a/MediaBrowser.Controller/Entities/IHasUserData.cs b/MediaBrowser.Controller/Entities/IHasUserData.cs new file mode 100644 index 000000000..780181a61 --- /dev/null +++ b/MediaBrowser.Controller/Entities/IHasUserData.cs @@ -0,0 +1,15 @@ + +namespace MediaBrowser.Controller.Entities +{ + /// <summary> + /// Interface IHasUserData + /// </summary> + public interface IHasUserData + { + /// <summary> + /// Gets the user data key. + /// </summary> + /// <returns>System.String.</returns> + string GetUserDataKey(); + } +} diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index a1154482c..6144bdd71 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -1,5 +1,6 @@ -using System; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; +using System; using System.Collections.Generic; namespace MediaBrowser.Controller.Entities.Movies @@ -7,7 +8,7 @@ namespace MediaBrowser.Controller.Entities.Movies /// <summary> /// Class BoxSet /// </summary> - public class BoxSet : Folder, IHasTrailers, IHasTags + public class BoxSet : Folder, IHasTrailers, IHasTags, IHasPreferredMetadataLanguage { public BoxSet() { @@ -29,5 +30,18 @@ namespace MediaBrowser.Controller.Entities.Movies /// </summary> /// <value>The tags.</value> public List<string> Tags { get; set; } + + public string PreferredMetadataLanguage { get; set; } + + /// <summary> + /// Gets or sets the preferred metadata country code. + /// </summary> + /// <value>The preferred metadata country code.</value> + public string PreferredMetadataCountryCode { get; set; } + + protected override bool GetBlockUnratedValue(UserConfiguration config) + { + return config.BlockUnratedMovies; + } } } diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index b4cf6c047..f9d3f845c 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; using System.IO; @@ -11,7 +12,7 @@ namespace MediaBrowser.Controller.Entities.Movies /// <summary> /// Class Movie /// </summary> - public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasTags + public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasTags, IHasPreferredMetadataLanguage { public List<Guid> SpecialFeatureIds { get; set; } @@ -19,6 +20,14 @@ namespace MediaBrowser.Controller.Entities.Movies public List<Guid> ThemeSongIds { get; set; } public List<Guid> ThemeVideoIds { get; set; } + + /// <summary> + /// Gets or sets the preferred metadata country code. + /// </summary> + /// <value>The preferred metadata country code.</value> + public string PreferredMetadataCountryCode { get; set; } + + public string PreferredMetadataLanguage { get; set; } public Movie() { @@ -180,5 +189,9 @@ namespace MediaBrowser.Controller.Entities.Movies }); } + protected override bool GetBlockUnratedValue(UserConfiguration config) + { + return config.BlockUnratedMovies; + } } } diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs index 68ad4630a..d9eff8fbe 100644 --- a/MediaBrowser.Controller/Entities/MusicVideo.cs +++ b/MediaBrowser.Controller/Entities/MusicVideo.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using System; @@ -48,5 +49,10 @@ namespace MediaBrowser.Controller.Entities { return this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? base.GetUserDataKey(); } + + protected override bool GetBlockUnratedValue(UserConfiguration config) + { + return config.BlockUnratedMusic; + } } } diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 7f94ab8e8..42897e09f 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; +using MediaBrowser.Model.Configuration; namespace MediaBrowser.Controller.Entities.TV { @@ -292,5 +293,10 @@ namespace MediaBrowser.Controller.Entities.TV { return new[] { Path }; } + + protected override bool GetBlockUnratedValue(UserConfiguration config) + { + return config.BlockUnratedSeries; + } } } diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 5727b316b..2d781118e 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using System; @@ -260,5 +261,11 @@ namespace MediaBrowser.Controller.Entities.TV { return GetEpisodes(user); } + + protected override bool GetBlockUnratedValue(UserConfiguration 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 1565de4f8..f7e78ccd4 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using System; @@ -13,13 +14,19 @@ namespace MediaBrowser.Controller.Entities.TV /// <summary> /// Class Series /// </summary> - public class Series : Folder, IHasSoundtracks, IHasTrailers, IHasTags + public class Series : Folder, IHasSoundtracks, IHasTrailers, IHasTags, IHasPreferredMetadataLanguage { public List<Guid> SpecialFeatureIds { get; set; } public List<Guid> SoundtrackIds { get; set; } public int SeasonCount { get; set; } + /// <summary> + /// Gets or sets the preferred metadata country code. + /// </summary> + /// <value>The preferred metadata country code.</value> + public string PreferredMetadataCountryCode { get; set; } + public Series() { AirDays = new List<DayOfWeek>(); @@ -183,7 +190,9 @@ namespace MediaBrowser.Controller.Entities.TV episodes = episodes.Where(i => !i.IsVirtualUnaired); } - return LibraryManager.Sort(episodes, user, new[] { ItemSortBy.AiredEpisodeOrder }, SortOrder.Ascending) + var sortBy = seasonNumber == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder; + + return LibraryManager.Sort(episodes, user, new[] { sortBy }, SortOrder.Ascending) .Cast<Episode>(); } @@ -215,5 +224,12 @@ namespace MediaBrowser.Controller.Entities.TV return false; }); } + + protected override bool GetBlockUnratedValue(UserConfiguration config) + { + return config.BlockUnratedSeries; + } + + public string PreferredMetadataLanguage { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs index 591fea14a..7000d04d3 100644 --- a/MediaBrowser.Controller/Entities/Trailer.cs +++ b/MediaBrowser.Controller/Entities/Trailer.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; using System.Runtime.Serialization; @@ -8,9 +9,17 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Class Trailer /// </summary> - public class Trailer : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers, IHasTaglines, IHasTags + public class Trailer : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers, IHasTaglines, IHasTags, IHasPreferredMetadataLanguage { public List<Guid> SoundtrackIds { get; set; } + + public string PreferredMetadataLanguage { get; set; } + + /// <summary> + /// Gets or sets the preferred metadata country code. + /// </summary> + /// <value>The preferred metadata country code.</value> + public string PreferredMetadataCountryCode { get; set; } public Trailer() { @@ -113,5 +122,10 @@ namespace MediaBrowser.Controller.Entities return base.GetUserDataKey(); } + + protected override bool GetBlockUnratedValue(UserConfiguration config) + { + return config.BlockUnratedTrailers; + } } } diff --git a/MediaBrowser.Controller/Kernel.cs b/MediaBrowser.Controller/Kernel.cs deleted file mode 100644 index 37a1648c1..000000000 --- a/MediaBrowser.Controller/Kernel.cs +++ /dev/null @@ -1,30 +0,0 @@ -using MediaBrowser.Controller.MediaInfo; - -namespace MediaBrowser.Controller -{ - /// <summary> - /// Class Kernel - /// </summary> - public class Kernel - { - /// <summary> - /// Gets the instance. - /// </summary> - /// <value>The instance.</value> - public static Kernel Instance { get; private set; } - - /// <summary> - /// Gets the FFMPEG controller. - /// </summary> - /// <value>The FFMPEG controller.</value> - public FFMpegManager FFMpegManager { get; set; } - - /// <summary> - /// Creates a kernel based on a Data path, which is akin to our current programdata path - /// </summary> - public Kernel() - { - Instance = this; - } - } -} diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 338edd568..ae34621cb 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -116,6 +116,11 @@ namespace MediaBrowser.Controller.Library Task ValidateMediaLibrary(IProgress<double> progress, CancellationToken cancellationToken); /// <summary> + /// Queues the library scan. + /// </summary> + void QueueLibraryScan(); + + /// <summary> /// Gets the default view. /// </summary> /// <returns>IEnumerable{VirtualFolderInfo}.</returns> diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs index d6d5f99aa..2bec9e3de 100644 --- a/MediaBrowser.Controller/Library/IUserDataManager.cs +++ b/MediaBrowser.Controller/Library/IUserDataManager.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Controller.Library /// <param name="reason">The reason.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task SaveUserData(Guid userId, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken); + Task SaveUserData(Guid userId, IHasUserData item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken); /// <summary> /// Gets the user data. diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index 65308bd10..d7504a86e 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -32,7 +32,9 @@ namespace MediaBrowser.Controller.Library "sæson", "temporada", "saison", - "staffel" + "staffel", + "series", + "сезон" }; /// <summary> @@ -122,6 +124,11 @@ namespace MediaBrowser.Controller.Library { var filename = Path.GetFileName(path); + if (string.Equals(path, "specials", StringComparison.OrdinalIgnoreCase)) + { + return 0; + } + // Look for one of the season folder names foreach (var name in SeasonFolderNames) { diff --git a/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs b/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs index 87e7f647a..ba328ff75 100644 --- a/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs +++ b/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs @@ -37,6 +37,6 @@ namespace MediaBrowser.Controller.Library /// Gets or sets the item. /// </summary> /// <value>The item.</value> - public BaseItem Item { get; set; } + public IHasUserData Item { get; set; } } } diff --git a/MediaBrowser.Controller/LiveTv/Channel.cs b/MediaBrowser.Controller/LiveTv/Channel.cs deleted file mode 100644 index 7186cfaf3..000000000 --- a/MediaBrowser.Controller/LiveTv/Channel.cs +++ /dev/null @@ -1,75 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.LiveTv; -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace MediaBrowser.Controller.LiveTv -{ - public class Channel : BaseItem, IItemByName - { - public Channel() - { - UserItemCountList = new List<ItemByNameCounts>(); - } - - /// <summary> - /// Gets the user data key. - /// </summary> - /// <returns>System.String.</returns> - public override string GetUserDataKey() - { - return "Channel-" + Name; - } - - [IgnoreDataMember] - public List<ItemByNameCounts> UserItemCountList { get; set; } - - /// <summary> - /// Gets or sets the number. - /// </summary> - /// <value>The number.</value> - public string ChannelNumber { get; set; } - - /// <summary> - /// Get or sets the Id. - /// </summary> - /// <value>The id of the channel.</value> - public string ChannelId { get; set; } - - /// <summary> - /// Gets or sets the name of the service. - /// </summary> - /// <value>The name of the service.</value> - public string ServiceName { get; set; } - - /// <summary> - /// Gets or sets the type of the channel. - /// </summary> - /// <value>The type of the channel.</value> - public ChannelType ChannelType { get; set; } - - public bool? HasProviderImage { get; set; } - - protected override string CreateSortName() - { - double number = 0; - - if (!string.IsNullOrEmpty(ChannelNumber)) - { - double.TryParse(ChannelNumber, out number); - } - - return number.ToString("000-") + (Name ?? string.Empty); - } - - public override string MediaType - { - get - { - return ChannelType == ChannelType.Radio ? Model.Entities.MediaType.Audio : Model.Entities.MediaType.Video; - } - } - } -} diff --git a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs index bb0636673..cdc9c76c8 100644 --- a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs @@ -32,9 +32,22 @@ namespace MediaBrowser.Controller.LiveTv public ChannelType ChannelType { get; set; } /// <summary> - /// Set this value to true or false if it is known via channel info whether there is an image or not. - /// Leave it null if the only way to determine is by requesting the image and handling the failure. + /// Supply the image path if it can be accessed directly from the file system /// </summary> + /// <value>The image path.</value> + public string ImagePath { get; set; } + + /// <summary> + /// Supply the image url if it can be downloaded + /// </summary> + /// <value>The image URL.</value> + public string ImageUrl { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance has image. + /// </summary> + /// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value> public bool? HasImage { get; set; } + } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 9ed7b633d..1d98dc7cf 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Entities; +using System.IO; +using MediaBrowser.Controller.Entities; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Querying; using System.Collections.Generic; @@ -25,13 +26,21 @@ namespace MediaBrowser.Controller.LiveTv IReadOnlyList<ILiveTvService> Services { get; } /// <summary> - /// Schedules the recording. + /// Gets the new timer defaults asynchronous. /// </summary> - /// <param name="programId">The program identifier.</param> - /// <returns>Task.</returns> - Task ScheduleRecording(string programId); + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{TimerInfo}.</returns> + Task<SeriesTimerInfoDto> GetNewTimerDefaults(CancellationToken cancellationToken); /// <summary> + /// Gets the new timer defaults. + /// </summary> + /// <param name="programId">The program identifier.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{SeriesTimerInfoDto}.</returns> + Task<SeriesTimerInfoDto> GetNewTimerDefaults(string programId, CancellationToken cancellationToken); + + /// <summary> /// Deletes the recording. /// </summary> /// <param name="id">The identifier.</param> @@ -46,6 +55,13 @@ namespace MediaBrowser.Controller.LiveTv Task CancelTimer(string id); /// <summary> + /// Cancels the series timer. + /// </summary> + /// <param name="id">The identifier.</param> + /// <returns>Task.</returns> + Task CancelSeriesTimer(string id); + + /// <summary> /// Adds the parts. /// </summary> /// <param name="services">The services.</param> @@ -122,9 +138,41 @@ namespace MediaBrowser.Controller.LiveTv /// </summary> /// <param name="id">The identifier.</param> /// <returns>Channel.</returns> - Channel GetChannel(string id); + LiveTvChannel GetInternalChannel(string id); + + /// <summary> + /// Gets the internal program. + /// </summary> + /// <param name="id">The identifier.</param> + /// <returns>LiveTvProgram.</returns> + LiveTvProgram GetInternalProgram(string id); + + /// <summary> + /// Gets the recording. + /// </summary> + /// <param name="id">The identifier.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>LiveTvRecording.</returns> + Task<LiveTvRecording> GetInternalRecording(string id, CancellationToken cancellationToken); /// <summary> + /// Gets the recording stream. + /// </summary> + /// <param name="id">The identifier.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{Stream}.</returns> + Task<StreamResponseInfo> GetRecordingStream(string id, CancellationToken cancellationToken); + + /// <summary> + /// Gets the program. + /// </summary> + /// <param name="id">The identifier.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="user">The user.</param> + /// <returns>Task{ProgramInfoDto}.</returns> + Task<ProgramInfoDto> GetProgram(string id, CancellationToken cancellationToken, User user = null); + + /// <summary> /// Gets the programs. /// </summary> /// <param name="query">The query.</param> @@ -147,5 +195,21 @@ namespace MediaBrowser.Controller.LiveTv /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> Task UpdateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken); + + /// <summary> + /// Creates the timer. + /// </summary> + /// <param name="timer">The timer.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + Task CreateTimer(TimerInfoDto timer, CancellationToken cancellationToken); + + /// <summary> + /// Creates the series timer. + /// </summary> + /// <param name="timer">The timer.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs index 8b1801f9e..31dbd8e99 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs @@ -31,6 +31,14 @@ namespace MediaBrowser.Controller.LiveTv Task CancelTimerAsync(string timerId, CancellationToken cancellationToken); /// <summary> + /// Cancels the series timer asynchronous. + /// </summary> + /// <param name="timerId">The timer identifier.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + Task CancelSeriesTimerAsync(string timerId, CancellationToken cancellationToken); + + /// <summary> /// Deletes the recording asynchronous. /// </summary> /// <param name="recordingId">The recording identifier.</param> @@ -71,28 +79,29 @@ namespace MediaBrowser.Controller.LiveTv Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken); /// <summary> - /// Gets the channel image asynchronous. + /// Gets the channel image asynchronous. This only needs to be implemented if an image path or url cannot be supplied to ChannelInfo /// </summary> /// <param name="channelId">The channel identifier.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{Stream}.</returns> - Task<ImageResponseInfo> GetChannelImageAsync(string channelId, CancellationToken cancellationToken); + Task<StreamResponseInfo> GetChannelImageAsync(string channelId, CancellationToken cancellationToken); /// <summary> - /// Gets the recording image asynchronous. + /// Gets the recording image asynchronous. This only needs to be implemented if an image path or url cannot be supplied to RecordingInfo /// </summary> - /// <param name="channelId">The channel identifier.</param> + /// <param name="recordingId">The recording identifier.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{ImageResponseInfo}.</returns> - Task<ImageResponseInfo> GetRecordingImageAsync(string channelId, CancellationToken cancellationToken); + Task<StreamResponseInfo> GetRecordingImageAsync(string recordingId, CancellationToken cancellationToken); /// <summary> - /// Gets the program image asynchronous. + /// Gets the program image asynchronous. This only needs to be implemented if an image path or url cannot be supplied to ProgramInfo /// </summary> + /// <param name="programId">The program identifier.</param> /// <param name="channelId">The channel identifier.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{ImageResponseInfo}.</returns> - Task<ImageResponseInfo> GetProgramImageAsync(string channelId, CancellationToken cancellationToken); + Task<StreamResponseInfo> GetProgramImageAsync(string programId, string channelId, CancellationToken cancellationToken); /// <summary> /// Gets the recordings asynchronous. @@ -109,6 +118,13 @@ namespace MediaBrowser.Controller.LiveTv Task<IEnumerable<TimerInfo>> GetTimersAsync(CancellationToken cancellationToken); /// <summary> + /// Gets the timer defaults asynchronous. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{TimerInfo}.</returns> + Task<SeriesTimerInfo> GetNewTimerDefaultsAsync(CancellationToken cancellationToken); + + /// <summary> /// Gets the series timers asynchronous. /// </summary> /// <param name="cancellationToken">The cancellation token.</param> @@ -122,5 +138,21 @@ namespace MediaBrowser.Controller.LiveTv /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{IEnumerable{ProgramInfo}}.</returns> Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, CancellationToken cancellationToken); + + /// <summary> + /// Gets the recording stream. + /// </summary> + /// <param name="recordingId">The recording identifier.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{Stream}.</returns> + Task<StreamResponseInfo> GetRecordingStream(string recordingId, CancellationToken cancellationToken); + + /// <summary> + /// Gets the channel stream. + /// </summary> + /// <param name="recordingId">The recording identifier.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{Stream}.</returns> + Task<StreamResponseInfo> GetChannelStream(string recordingId, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs new file mode 100644 index 000000000..1e6d74ce8 --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -0,0 +1,57 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.LiveTv; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace MediaBrowser.Controller.LiveTv +{ + public class LiveTvChannel : BaseItem, IItemByName + { + public LiveTvChannel() + { + UserItemCountList = new List<ItemByNameCounts>(); + } + + /// <summary> + /// Gets the user data key. + /// </summary> + /// <returns>System.String.</returns> + public override string GetUserDataKey() + { + return GetClientTypeName() + "-" + Name; + } + + [IgnoreDataMember] + public List<ItemByNameCounts> UserItemCountList { get; set; } + + public ChannelInfo ChannelInfo { get; set; } + + public string ServiceName { get; set; } + + protected override string CreateSortName() + { + double number = 0; + + if (!string.IsNullOrEmpty(ChannelInfo.Number)) + { + double.TryParse(ChannelInfo.Number, out number); + } + + return number.ToString("000-") + (Name ?? string.Empty); + } + + public override string MediaType + { + get + { + return ChannelInfo.ChannelType == ChannelType.Radio ? Model.Entities.MediaType.Audio : Model.Entities.MediaType.Video; + } + } + + public override string GetClientTypeName() + { + return "Channel"; + } + } +} diff --git a/MediaBrowser.Controller/LiveTv/LiveTvException.cs b/MediaBrowser.Controller/LiveTv/LiveTvException.cs new file mode 100644 index 000000000..0a68180ca --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/LiveTvException.cs @@ -0,0 +1,18 @@ +using System; + +namespace MediaBrowser.Controller.LiveTv +{ + /// <summary> + /// Class LiveTvException. + /// </summary> + public class LiveTvException : Exception + { + } + + /// <summary> + /// Class LiveTvConflictException. + /// </summary> + public class LiveTvConflictException : LiveTvException + { + } +} diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs new file mode 100644 index 000000000..abacc0c18 --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -0,0 +1,36 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.LiveTv; + +namespace MediaBrowser.Controller.LiveTv +{ + public class LiveTvProgram : BaseItem + { + /// <summary> + /// Gets the user data key. + /// </summary> + /// <returns>System.String.</returns> + public override string GetUserDataKey() + { + return GetClientTypeName() + "-" + Name; + } + + public ProgramInfo ProgramInfo { get; set; } + + public ChannelType ChannelType { get; set; } + + public string ServiceName { get; set; } + + public override string MediaType + { + get + { + return ChannelType == ChannelType.TV ? Model.Entities.MediaType.Video : Model.Entities.MediaType.Audio; + } + } + + public override string GetClientTypeName() + { + return "Program"; + } + } +} diff --git a/MediaBrowser.Controller/LiveTv/LiveTvRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvRecording.cs new file mode 100644 index 000000000..1c453ab5a --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/LiveTvRecording.cs @@ -0,0 +1,43 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.LiveTv; + +namespace MediaBrowser.Controller.LiveTv +{ + public class LiveTvRecording : BaseItem + { + /// <summary> + /// Gets the user data key. + /// </summary> + /// <returns>System.String.</returns> + public override string GetUserDataKey() + { + return GetClientTypeName() + "-" + Name; + } + + public RecordingInfo RecordingInfo { get; set; } + + public string ServiceName { get; set; } + + public override string MediaType + { + get + { + return RecordingInfo.ChannelType == ChannelType.Radio ? Model.Entities.MediaType.Audio : Model.Entities.MediaType.Video; + } + } + + public override LocationType LocationType + { + get + { + return LocationType.Remote; + } + } + + public override string GetClientTypeName() + { + return "Recording"; + } + } +} diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs index ce7a4a598..2c7b40415 100644 --- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs @@ -92,11 +92,65 @@ namespace MediaBrowser.Controller.LiveTv public string EpisodeTitle { get; set; } /// <summary> - /// Set this value to true or false if it is known via program info whether there is an image or not. - /// Leave it null if the only way to determine is by requesting the image and handling the failure. + /// Supply the image path if it can be accessed directly from the file system /// </summary> + /// <value>The image path.</value> + public string ImagePath { get; set; } + + /// <summary> + /// Supply the image url if it can be downloaded + /// </summary> + /// <value>The image URL.</value> + public string ImageUrl { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is movie. + /// </summary> + /// <value><c>true</c> if this instance is movie; otherwise, <c>false</c>.</value> + public bool IsMovie { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is sports. + /// </summary> + /// <value><c>true</c> if this instance is sports; otherwise, <c>false</c>.</value> + public bool IsSports { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is series. + /// </summary> + /// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value> + public bool IsSeries { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is live. + /// </summary> + /// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value> + public bool IsLive { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is news. + /// </summary> + /// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value> + public bool IsNews { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is kids. + /// </summary> + /// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value> + public bool IsKids { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is premiere. + /// </summary> + /// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value> + public bool IsPremiere { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance has image. + /// </summary> + /// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value> public bool? HasImage { get; set; } - + public ProgramInfo() { Genres = new List<string>(); diff --git a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs index 4fc8c0f7a..6a0d135c8 100644 --- a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs +++ b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs @@ -12,14 +12,15 @@ namespace MediaBrowser.Controller.LiveTv public string Id { get; set; } /// <summary> - /// ChannelId of the recording. + /// Gets or sets the series timer identifier. /// </summary> - public string ChannelId { get; set; } - + /// <value>The series timer identifier.</value> + public string SeriesTimerId { get; set; } + /// <summary> - /// ChannelName of the recording. + /// ChannelId of the recording. /// </summary> - public string ChannelName { get; set; } + public string ChannelId { get; set; } /// <summary> /// Gets or sets the type of the channel. @@ -39,6 +40,12 @@ namespace MediaBrowser.Controller.LiveTv public string Path { get; set; } /// <summary> + /// Gets or sets the URL. + /// </summary> + /// <value>The URL.</value> + public string Url { get; set; } + + /// <summary> /// Gets or sets the overview. /// </summary> /// <value>The overview.</value> @@ -96,6 +103,48 @@ namespace MediaBrowser.Controller.LiveTv public ProgramAudio? Audio { get; set; } /// <summary> + /// Gets or sets a value indicating whether this instance is movie. + /// </summary> + /// <value><c>true</c> if this instance is movie; otherwise, <c>false</c>.</value> + public bool IsMovie { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is sports. + /// </summary> + /// <value><c>true</c> if this instance is sports; otherwise, <c>false</c>.</value> + public bool IsSports { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is series. + /// </summary> + /// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value> + public bool IsSeries { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is live. + /// </summary> + /// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value> + public bool IsLive { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is news. + /// </summary> + /// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value> + public bool IsNews { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is kids. + /// </summary> + /// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value> + public bool IsKids { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is premiere. + /// </summary> + /// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value> + public bool IsPremiere { get; set; } + + /// <summary> /// Gets or sets the official rating. /// </summary> /// <value>The official rating.</value> @@ -108,11 +157,24 @@ namespace MediaBrowser.Controller.LiveTv public float? CommunityRating { get; set; } /// <summary> - /// Set this value to true or false if it is known via recording info whether there is an image or not. - /// Leave it null if the only way to determine is by requesting the image and handling the failure. + /// Supply the image path if it can be accessed directly from the file system + /// </summary> + /// <value>The image path.</value> + public string ImagePath { get; set; } + + /// <summary> + /// Supply the image url if it can be downloaded /// </summary> + /// <value>The image URL.</value> + public string ImageUrl { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance has image. + /// </summary> + /// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value> public bool? HasImage { get; set; } + public RecordingInfo() { Genres = new List<string>(); diff --git a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs index 607282796..1be6549ff 100644 --- a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Model.LiveTv; -using System; +using System; using System.Collections.Generic; namespace MediaBrowser.Controller.LiveTv @@ -17,11 +16,6 @@ namespace MediaBrowser.Controller.LiveTv public string ChannelId { get; set; } /// <summary> - /// ChannelName of the recording. - /// </summary> - public string ChannelName { get; set; } - - /// <summary> /// Gets or sets the program identifier. /// </summary> /// <value>The program identifier.</value> @@ -48,12 +42,24 @@ namespace MediaBrowser.Controller.LiveTv public DateTime EndDate { get; set; } /// <summary> - /// Gets or sets the type of the recurrence. + /// Gets or sets a value indicating whether [record any time]. /// </summary> - /// <value>The type of the recurrence.</value> - public RecurrenceType RecurrenceType { get; set; } + /// <value><c>true</c> if [record any time]; otherwise, <c>false</c>.</value> + public bool RecordAnyTime { get; set; } /// <summary> + /// Gets or sets a value indicating whether [record any channel]. + /// </summary> + /// <value><c>true</c> if [record any channel]; otherwise, <c>false</c>.</value> + public bool RecordAnyChannel { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether [record new only]. + /// </summary> + /// <value><c>true</c> if [record new only]; otherwise, <c>false</c>.</value> + public bool RecordNewOnly { get; set; } + + /// <summary> /// Gets or sets the days. /// </summary> /// <value>The days.</value> @@ -66,28 +72,28 @@ namespace MediaBrowser.Controller.LiveTv public int Priority { get; set; } /// <summary> - /// Gets or sets the requested pre padding seconds. + /// Gets or sets the pre padding seconds. /// </summary> - /// <value>The requested pre padding seconds.</value> - public int RequestedPrePaddingSeconds { get; set; } + /// <value>The pre padding seconds.</value> + public int PrePaddingSeconds { get; set; } /// <summary> - /// Gets or sets the requested post padding seconds. + /// Gets or sets the post padding seconds. /// </summary> - /// <value>The requested post padding seconds.</value> - public int RequestedPostPaddingSeconds { get; set; } + /// <value>The post padding seconds.</value> + public int PostPaddingSeconds { get; set; } /// <summary> - /// Gets or sets the required pre padding seconds. + /// Gets or sets a value indicating whether this instance is pre padding required. /// </summary> - /// <value>The required pre padding seconds.</value> - public int RequiredPrePaddingSeconds { get; set; } + /// <value><c>true</c> if this instance is pre padding required; otherwise, <c>false</c>.</value> + public bool IsPrePaddingRequired { get; set; } /// <summary> - /// Gets or sets the required post padding seconds. + /// Gets or sets a value indicating whether this instance is post padding required. /// </summary> - /// <value>The required post padding seconds.</value> - public int RequiredPostPaddingSeconds { get; set; } + /// <value><c>true</c> if this instance is post padding required; otherwise, <c>false</c>.</value> + public bool IsPostPaddingRequired { get; set; } public SeriesTimerInfo() { diff --git a/MediaBrowser.Controller/LiveTv/ImageResponseInfo.cs b/MediaBrowser.Controller/LiveTv/StreamResponseInfo.cs index d454a1ef8..c3b438c5e 100644 --- a/MediaBrowser.Controller/LiveTv/ImageResponseInfo.cs +++ b/MediaBrowser.Controller/LiveTv/StreamResponseInfo.cs @@ -2,7 +2,7 @@ namespace MediaBrowser.Controller.LiveTv { - public class ImageResponseInfo + public class StreamResponseInfo { /// <summary> /// Gets or sets the stream. diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs index 786858e09..5d92a212f 100644 --- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs @@ -22,11 +22,6 @@ namespace MediaBrowser.Controller.LiveTv public string ChannelId { get; set; } /// <summary> - /// ChannelName of the recording. - /// </summary> - public string ChannelName { get; set; } - - /// <summary> /// Gets or sets the program identifier. /// </summary> /// <value>The program identifier.</value> @@ -59,27 +54,33 @@ namespace MediaBrowser.Controller.LiveTv public RecordingStatus Status { get; set; } /// <summary> - /// Gets or sets the requested pre padding seconds. + /// Gets or sets the pre padding seconds. /// </summary> - /// <value>The requested pre padding seconds.</value> - public int RequestedPrePaddingSeconds { get; set; } + /// <value>The pre padding seconds.</value> + public int PrePaddingSeconds { get; set; } /// <summary> - /// Gets or sets the requested post padding seconds. + /// Gets or sets the post padding seconds. /// </summary> - /// <value>The requested post padding seconds.</value> - public int RequestedPostPaddingSeconds { get; set; } + /// <value>The post padding seconds.</value> + public int PostPaddingSeconds { get; set; } /// <summary> - /// Gets or sets the required pre padding seconds. + /// Gets or sets a value indicating whether this instance is pre padding required. /// </summary> - /// <value>The required pre padding seconds.</value> - public int RequiredPrePaddingSeconds { get; set; } + /// <value><c>true</c> if this instance is pre padding required; otherwise, <c>false</c>.</value> + public bool IsPrePaddingRequired { get; set; } /// <summary> - /// Gets or sets the required post padding seconds. + /// Gets or sets a value indicating whether this instance is post padding required. + /// </summary> + /// <value><c>true</c> if this instance is post padding required; otherwise, <c>false</c>.</value> + public bool IsPostPaddingRequired { get; set; } + + /// <summary> + /// Gets or sets the priority. /// </summary> - /// <value>The required post padding seconds.</value> - public int RequiredPostPaddingSeconds { get; set; } + /// <value>The priority.</value> + public int Priority { get; set; } } } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index df108b590..0c5c0a5cd 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -85,8 +85,9 @@ <Compile Include="Entities\IHasAspectRatio.cs" /> <Compile Include="Entities\IHasBudget.cs" /> <Compile Include="Entities\IHasCriticRating.cs" /> - <Compile Include="Entities\IHasLanguage.cs" /> + <Compile Include="Entities\IHasImages.cs" /> <Compile Include="Entities\IHasMediaStreams.cs" /> + <Compile Include="Entities\IHasPreferredMetadataLanguage.cs" /> <Compile Include="Entities\IHasProductionLocations.cs" /> <Compile Include="Entities\IHasScreenshots.cs" /> <Compile Include="Entities\IHasSoundtracks.cs" /> @@ -94,6 +95,7 @@ <Compile Include="Entities\IHasTags.cs" /> <Compile Include="Entities\IHasThemeMedia.cs" /> <Compile Include="Entities\IHasTrailers.cs" /> + <Compile Include="Entities\IHasUserData.cs" /> <Compile Include="Entities\IItemByName.cs" /> <Compile Include="Entities\ILibraryItem.cs" /> <Compile Include="Entities\ImageSourceInfo.cs" /> @@ -106,11 +108,14 @@ <Compile Include="Library\ItemUpdateType.cs" /> <Compile Include="Library\IUserDataManager.cs" /> <Compile Include="Library\UserDataSaveEventArgs.cs" /> - <Compile Include="LiveTv\Channel.cs" /> + <Compile Include="LiveTv\LiveTvChannel.cs" /> <Compile Include="LiveTv\ChannelInfo.cs" /> <Compile Include="LiveTv\ILiveTvManager.cs" /> <Compile Include="LiveTv\ILiveTvService.cs" /> - <Compile Include="LiveTv\ImageResponseInfo.cs" /> + <Compile Include="LiveTv\LiveTvException.cs" /> + <Compile Include="LiveTv\StreamResponseInfo.cs" /> + <Compile Include="LiveTv\LiveTvProgram.cs" /> + <Compile Include="LiveTv\LiveTvRecording.cs" /> <Compile Include="LiveTv\ProgramInfo.cs" /> <Compile Include="LiveTv\RecordingInfo.cs" /> <Compile Include="LiveTv\SeriesTimerInfo.cs" /> @@ -188,10 +193,10 @@ <Compile Include="Library\TVUtils.cs" /> <Compile Include="Library\ItemResolveArgs.cs" /> <Compile Include="IO\FileData.cs" /> - <Compile Include="Kernel.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Providers\BaseMetadataProvider.cs" /> <Compile Include="Session\ISessionController.cs" /> + <Compile Include="Session\ISessionControllerFactory.cs" /> <Compile Include="Session\PlaybackInfo.cs" /> <Compile Include="Session\PlaybackProgressInfo.cs" /> <Compile Include="Session\PlaybackStopInfo.cs" /> diff --git a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs index e53acfc02..ced53299d 100644 --- a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs +++ b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs @@ -1,12 +1,16 @@ -using MediaBrowser.Common.IO; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.IO; using MediaBrowser.Common.MediaInfo; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Persistence; 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.Threading; @@ -19,79 +23,89 @@ namespace MediaBrowser.Controller.MediaInfo /// </summary> public class FFMpegManager { - /// <summary> - /// Gets or sets the video image cache. - /// </summary> - /// <value>The video image cache.</value> - internal FileSystemRepository VideoImageCache { get; set; } - - /// <summary> - /// Gets or sets the subtitle cache. - /// </summary> - /// <value>The subtitle cache.</value> - internal FileSystemRepository SubtitleCache { get; set; } - - private readonly IServerApplicationPaths _appPaths; + private readonly IServerConfigurationManager _config; private readonly IMediaEncoder _encoder; private readonly ILogger _logger; private readonly IItemRepository _itemRepo; private readonly IFileSystem _fileSystem; + public static FFMpegManager Instance { get; private set; } + /// <summary> /// Initializes a new instance of the <see cref="FFMpegManager" /> class. /// </summary> - /// <param name="appPaths">The app paths.</param> /// <param name="encoder">The encoder.</param> /// <param name="logger">The logger.</param> /// <param name="itemRepo">The item repo.</param> /// <exception cref="System.ArgumentNullException">zipClient</exception> - public FFMpegManager(IServerApplicationPaths appPaths, IMediaEncoder encoder, ILogger logger, IItemRepository itemRepo, IFileSystem fileSystem) + public FFMpegManager(IMediaEncoder encoder, ILogger logger, IItemRepository itemRepo, IFileSystem fileSystem, IServerConfigurationManager config) { - _appPaths = appPaths; _encoder = encoder; _logger = logger; _itemRepo = itemRepo; _fileSystem = fileSystem; + _config = config; - VideoImageCache = new FileSystemRepository(VideoImagesDataPath); - SubtitleCache = new FileSystemRepository(SubtitleCachePath); + // TODO: Remove this static instance + Instance = this; } /// <summary> - /// Gets the video images data path. + /// Gets the chapter images data path. /// </summary> - /// <value>The video images data path.</value> - public string VideoImagesDataPath + /// <value>The chapter images data path.</value> + public string ChapterImagesPath { get { - return Path.Combine(_appPaths.DataPath, "extracted-video-images"); + return Path.Combine(_config.ApplicationPaths.DataPath, "chapter-images"); } } /// <summary> - /// Gets the audio images data path. + /// Gets the subtitle cache path. /// </summary> - /// <value>The audio images data path.</value> - public string AudioImagesDataPath + /// <value>The subtitle cache path.</value> + private string SubtitleCachePath { get { - return Path.Combine(_appPaths.DataPath, "extracted-audio-images"); + return Path.Combine(_config.ApplicationPaths.CachePath, "subtitles"); } } /// <summary> - /// Gets the subtitle cache path. + /// Determines whether [is eligible for chapter image extraction] [the specified video]. /// </summary> - /// <value>The subtitle cache path.</value> - public string SubtitleCachePath + /// <param name="video">The video.</param> + /// <returns><c>true</c> if [is eligible for chapter image extraction] [the specified video]; otherwise, <c>false</c>.</returns> + private bool IsEligibleForChapterImageExtraction(Video video) { - get + if (video is Movie) + { + if (!_config.Configuration.EnableMovieChapterImageExtraction) + { + return false; + } + } + else if (video is Episode) { - return Path.Combine(_appPaths.CachePath, "subtitles"); + if (!_config.Configuration.EnableEpisodeChapterImageExtraction) + { + return false; + } + } + else + { + if (!_config.Configuration.EnableOtherVideoChapterImageExtraction) + { + return false; + } } + + // Can't extract images if there are no video streams + return video.DefaultVideoStreamIndex.HasValue; } /// <summary> @@ -111,8 +125,7 @@ namespace MediaBrowser.Controller.MediaInfo /// <exception cref="System.ArgumentNullException"></exception> public async Task<bool> PopulateChapterImages(Video video, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken) { - // Can't extract images if there are no video streams - if (!video.DefaultVideoStreamIndex.HasValue) + if (!IsEligibleForChapterImageExtraction(video)) { return true; } @@ -122,6 +135,8 @@ namespace MediaBrowser.Controller.MediaInfo var runtimeTicks = video.RunTimeTicks ?? 0; + var currentImages = GetSavedChapterImages(video); + foreach (var chapter in chapters) { if (chapter.StartPositionTicks >= runtimeTicks) @@ -130,11 +145,9 @@ namespace MediaBrowser.Controller.MediaInfo break; } - var filename = video.Path + "_" + video.DateModified.Ticks + "_" + chapter.StartPositionTicks; - - var path = VideoImageCache.GetResourcePath(filename, ".jpg"); + var path = GetChapterImagePath(video, chapter.StartPositionTicks); - if (!File.Exists(path)) + if (!currentImages.Contains(path, StringComparer.OrdinalIgnoreCase)) { if (extractImages) { @@ -157,7 +170,7 @@ namespace MediaBrowser.Controller.MediaInfo InputType type; - var inputPath = MediaEncoderHelpers.GetInputArgument(video, null, out type); + var inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, false, video.VideoType, video.IsoType, null, video.PlayableStreamFileNames, out type); try { @@ -188,39 +201,87 @@ namespace MediaBrowser.Controller.MediaInfo await _itemRepo.SaveChapters(video.Id, chapters, cancellationToken).ConfigureAwait(false); } + DeleteDeadImages(currentImages, chapters); + return success; } + private void DeleteDeadImages(IEnumerable<string> images, IEnumerable<ChapterInfo> chapters) + { + var deadImages = images + .Except(chapters.Select(i => i.ImagePath).Where(i => !string.IsNullOrEmpty(i)), StringComparer.OrdinalIgnoreCase) + .Where(i => BaseItem.SupportedImageExtensions.Contains(Path.GetExtension(i), StringComparer.OrdinalIgnoreCase)) + .ToList(); + + foreach (var image in deadImages) + { + _logger.Debug("Deleting dead chapter image {0}", image); + + try + { + File.Delete(image); + } + catch (IOException ex) + { + _logger.ErrorException("Error deleting {0}.", ex, image); + } + } + } + + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + /// <summary> /// Gets the subtitle cache path. /// </summary> - /// <param name="input">The input.</param> - /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param> + /// <param name="mediaPath">The media path.</param> + /// <param name="subtitleStream">The subtitle stream.</param> /// <param name="offset">The offset.</param> /// <param name="outputExtension">The output extension.</param> /// <returns>System.String.</returns> - public string GetSubtitleCachePath(Video input, int subtitleStreamIndex, TimeSpan? offset, string outputExtension) + public string GetSubtitleCachePath(string mediaPath, MediaStream subtitleStream, TimeSpan? offset, string outputExtension) { var ticksParam = offset.HasValue ? "_" + offset.Value.Ticks : ""; - var stream = _itemRepo.GetMediaStreams(new MediaStreamQuery + if (subtitleStream.IsExternal) { - ItemId = input.Id, - Index = subtitleStreamIndex + ticksParam += _fileSystem.GetLastWriteTimeUtc(subtitleStream.Path).Ticks; + } + + var date = _fileSystem.GetLastWriteTimeUtc(mediaPath); - }).FirstOrDefault(); + var filename = (mediaPath + "_" + subtitleStream.Index.ToString(_usCulture) + "_" + date.Ticks.ToString(_usCulture) + ticksParam).GetMD5() + outputExtension; - if (stream == null) + var prefix = filename.Substring(0, 1); + + return Path.Combine(SubtitleCachePath, prefix, filename); + } + + public string GetChapterImagePath(Video video, long chapterPositionTicks) + { + var filename = video.DateModified.Ticks.ToString(_usCulture) + "_" + chapterPositionTicks.ToString(_usCulture) + ".jpg"; + + var videoId = video.Id.ToString(); + var prefix = videoId.Substring(0, 1); + + return Path.Combine(ChapterImagesPath, prefix, videoId, filename); + } + + public List<string> GetSavedChapterImages(Video video) + { + var videoId = video.Id.ToString(); + var prefix = videoId.Substring(0, 1); + + var path = Path.Combine(ChapterImagesPath, prefix, videoId); + + try { - return null; + return Directory.EnumerateFiles(path) + .ToList(); } - - if (stream.IsExternal) + catch (DirectoryNotFoundException) { - ticksParam += _fileSystem.GetLastWriteTimeUtc(stream.Path).Ticks; + return new List<string>(); } - - return SubtitleCache.GetResourcePath(input.Id + "_" + subtitleStreamIndex + "_" + input.DateModified.Ticks + ticksParam, outputExtension); } } } diff --git a/MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs b/MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs index 8c2f7c219..904ecdf93 100644 --- a/MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs +++ b/MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs @@ -1,5 +1,8 @@ -using MediaBrowser.Common.MediaInfo; -using MediaBrowser.Controller.Entities; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using MediaBrowser.Common.MediaInfo; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; @@ -13,43 +16,47 @@ namespace MediaBrowser.Controller.MediaInfo /// <summary> /// Gets the input argument. /// </summary> - /// <param name="video">The video.</param> + /// <param name="videoPath">The video path.</param> + /// <param name="isRemote">if set to <c>true</c> [is remote].</param> + /// <param name="videoType">Type of the video.</param> + /// <param name="isoType">Type of the iso.</param> /// <param name="isoMount">The iso mount.</param> + /// <param name="playableStreamFileNames">The playable stream file names.</param> /// <param name="type">The type.</param> /// <returns>System.String[][].</returns> - public static string[] GetInputArgument(Video video, IIsoMount isoMount, out InputType type) + public static string[] GetInputArgument(string videoPath, bool isRemote, VideoType videoType, IsoType? isoType, IIsoMount isoMount, IEnumerable<string> playableStreamFileNames, out InputType type) { - var inputPath = isoMount == null ? new[] { video.Path } : new[] { isoMount.MountedPath }; + var inputPath = isoMount == null ? new[] { videoPath } : new[] { isoMount.MountedPath }; type = InputType.VideoFile; - switch (video.VideoType) + switch (videoType) { case VideoType.BluRay: type = InputType.Bluray; break; case VideoType.Dvd: type = InputType.Dvd; - inputPath = video.GetPlayableStreamFiles(inputPath[0]).ToArray(); + inputPath = GetPlayableStreamFiles(inputPath[0], playableStreamFileNames).ToArray(); break; case VideoType.Iso: - if (video.IsoType.HasValue) + if (isoType.HasValue) { - switch (video.IsoType.Value) + switch (isoType.Value) { case IsoType.BluRay: type = InputType.Bluray; break; case IsoType.Dvd: type = InputType.Dvd; - inputPath = video.GetPlayableStreamFiles(inputPath[0]).ToArray(); + inputPath = GetPlayableStreamFiles(inputPath[0], playableStreamFileNames).ToArray(); break; } } break; case VideoType.VideoFile: { - if (video.LocationType == LocationType.Remote) + if (isRemote) { type = InputType.Url; } @@ -60,6 +67,17 @@ namespace MediaBrowser.Controller.MediaInfo return inputPath; } + public static List<string> GetPlayableStreamFiles(string rootPath, IEnumerable<string> filenames) + { + var allFiles = Directory + .EnumerateFiles(rootPath, "*", SearchOption.AllDirectories) + .ToList(); + + return filenames.Select(name => allFiles.FirstOrDefault(f => string.Equals(Path.GetFileName(f), name, StringComparison.OrdinalIgnoreCase))) + .Where(f => !string.IsNullOrEmpty(f)) + .ToList(); + } + /// <summary> /// Gets the type of the input. /// </summary> diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs index c9f57a927..799f339f1 100644 --- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs +++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs @@ -269,10 +269,10 @@ namespace MediaBrowser.Controller.Providers { var val = reader.ReadElementContentAsString(); - var hasLanguage = item as IHasLanguage; + var hasLanguage = item as IHasPreferredMetadataLanguage; if (hasLanguage != null) { - hasLanguage.Language = val; + hasLanguage.PreferredMetadataLanguage = val; } break; diff --git a/MediaBrowser.Controller/Providers/IImageEnhancer.cs b/MediaBrowser.Controller/Providers/IImageEnhancer.cs index 54ba6d322..ae605ec0d 100644 --- a/MediaBrowser.Controller/Providers/IImageEnhancer.cs +++ b/MediaBrowser.Controller/Providers/IImageEnhancer.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Controller.Providers /// <param name="item">The item.</param> /// <param name="imageType">Type of the image.</param> /// <returns><c>true</c> if this enhancer will enhance the supplied image for the supplied item, <c>false</c> otherwise</returns> - bool Supports(BaseItem item, ImageType imageType); + bool Supports(IHasImages item, ImageType imageType); /// <summary> /// Gets the priority or order in which this enhancer should be run. @@ -28,7 +28,7 @@ namespace MediaBrowser.Controller.Providers /// <param name="item">The item.</param> /// <param name="imageType">Type of the image.</param> /// <returns>Cache key relating to the current state of this item and configuration</returns> - string GetConfigurationCacheKey(BaseItem item, ImageType imageType); + string GetConfigurationCacheKey(IHasImages item, ImageType imageType); /// <summary> /// Gets the size of the enhanced image. @@ -38,7 +38,7 @@ namespace MediaBrowser.Controller.Providers /// <param name="imageIndex">Index of the image.</param> /// <param name="originalImageSize">Size of the original image.</param> /// <returns>ImageSize.</returns> - ImageSize GetEnhancedImageSize(BaseItem item, ImageType imageType, int imageIndex, ImageSize originalImageSize); + ImageSize GetEnhancedImageSize(IHasImages item, ImageType imageType, int imageIndex, ImageSize originalImageSize); /// <summary> /// Enhances the image async. @@ -49,6 +49,6 @@ namespace MediaBrowser.Controller.Providers /// <param name="imageIndex">Index of the image.</param> /// <returns>Task{Image}.</returns> /// <exception cref="System.ArgumentNullException"></exception> - Task<Image> EnhanceImageAsync(BaseItem item, Image originalImage, ImageType imageType, int imageIndex); + Task<Image> EnhanceImageAsync(IHasImages item, Image originalImage, ImageType imageType, int imageIndex); } }
\ No newline at end of file diff --git a/MediaBrowser.Controller/Providers/IImageProvider.cs b/MediaBrowser.Controller/Providers/IImageProvider.cs index d70532b59..ccf199844 100644 --- a/MediaBrowser.Controller/Providers/IImageProvider.cs +++ b/MediaBrowser.Controller/Providers/IImageProvider.cs @@ -23,7 +23,7 @@ namespace MediaBrowser.Controller.Providers /// </summary> /// <param name="item">The item.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - bool Supports(BaseItem item); + bool Supports(IHasImages item); /// <summary> /// Gets the images. @@ -32,7 +32,7 @@ namespace MediaBrowser.Controller.Providers /// <param name="imageType">Type of the image.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns> - Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken); + Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken); /// <summary> /// Gets the images. @@ -40,7 +40,7 @@ namespace MediaBrowser.Controller.Providers /// <param name="item">The item.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns> - Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken); + Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken); /// <summary> /// Gets the priority. diff --git a/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs b/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs index 7d9739448..3cd38da45 100644 --- a/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs +++ b/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs @@ -133,6 +133,19 @@ namespace MediaBrowser.Controller.Resolvers /// <param name="includeCreationTime">if set to <c>true</c> [include creation time].</param> public static void EnsureDates(IFileSystem fileSystem, BaseItem item, ItemResolveArgs args, bool includeCreationTime) { + if (fileSystem == null) + { + throw new ArgumentNullException("fileSystem"); + } + if (item == null) + { + throw new ArgumentNullException("item"); + } + if (args == null) + { + throw new ArgumentNullException("args"); + } + // See if a different path came out of the resolver than what went in if (!string.Equals(args.Path, item.Path, StringComparison.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.Controller/Session/ISessionControllerFactory.cs b/MediaBrowser.Controller/Session/ISessionControllerFactory.cs new file mode 100644 index 000000000..92862e462 --- /dev/null +++ b/MediaBrowser.Controller/Session/ISessionControllerFactory.cs @@ -0,0 +1,16 @@ + +namespace MediaBrowser.Controller.Session +{ + /// <summary> + /// Interface ISesssionControllerFactory + /// </summary> + public interface ISessionControllerFactory + { + /// <summary> + /// Gets the session controller. + /// </summary> + /// <param name="session">The session.</param> + /// <returns>ISessionController.</returns> + ISessionController GetSessionController(SessionInfo session); + } +} diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 771d8f72e..ec138bfb4 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -35,16 +35,23 @@ namespace MediaBrowser.Controller.Session IEnumerable<SessionInfo> Sessions { get; } /// <summary> + /// Adds the parts. + /// </summary> + /// <param name="sessionFactories">The session factories.</param> + void AddParts(IEnumerable<ISessionControllerFactory> sessionFactories); + + /// <summary> /// Logs the user activity. /// </summary> /// <param name="clientType">Type of the client.</param> /// <param name="appVersion">The app version.</param> /// <param name="deviceId">The device id.</param> /// <param name="deviceName">Name of the device.</param> + /// <param name="remoteEndPoint">The remote end point.</param> /// <param name="user">The user.</param> /// <returns>Task.</returns> /// <exception cref="System.ArgumentNullException">user</exception> - Task<SessionInfo> LogSessionActivity(string clientType, string appVersion, string deviceId, string deviceName, User user); + Task<SessionInfo> LogSessionActivity(string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user); /// <summary> /// Used to report that playback has started for an item diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index ed2fcda67..82e9328ac 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -15,6 +15,12 @@ namespace MediaBrowser.Controller.Session } /// <summary> + /// Gets or sets the remote end point. + /// </summary> + /// <value>The remote end point.</value> + public string RemoteEndPoint { get; set; } + + /// <summary> /// Gets or sets a value indicating whether this instance can seek. /// </summary> /// <value><c>true</c> if this instance can seek; otherwise, <c>false</c>.</value> |
