diff options
63 files changed, 1564 insertions, 357 deletions
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index f91a35dca..fa4969878 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1160,6 +1160,72 @@ namespace MediaBrowser.Api.Playback } /// <summary> + /// Parses the parameters. + /// </summary> + /// <param name="request">The request.</param> + private void ParseParams(StreamRequest request) + { + var vals = request.Params.Split(';'); + + var videoRequest = request as VideoStreamRequest; + + for (var i = 0; i < vals.Length; i++) + { + var val = vals[i]; + + if (string.IsNullOrWhiteSpace(val)) + { + continue; + } + + if (i == 0) + { + request.DeviceId = val; + } + else if (i == 1) + { + if (videoRequest != null) + { + videoRequest.VideoCodec = (VideoCodecs)Enum.Parse(typeof(VideoCodecs), val, true); + } + } + else if (i == 2) + { + request.AudioCodec = (AudioCodecs)Enum.Parse(typeof(AudioCodecs), val, true); + } + else if (i == 3) + { + if (videoRequest != null) + { + videoRequest.AudioStreamIndex = int.Parse(val, UsCulture); + } + } + else if (i == 4) + { + if (videoRequest != null) + { + videoRequest.SubtitleStreamIndex = int.Parse(val, UsCulture); + } + } + else if (i == 5) + { + if (videoRequest != null) + { + videoRequest.VideoBitRate = int.Parse(val, UsCulture); + } + } + else if (i == 6) + { + request.AudioBitRate = int.Parse(val, UsCulture); + } + else if (i == 7) + { + request.AudioChannels = int.Parse(val, UsCulture); + } + } + } + + /// <summary> /// Gets the state. /// </summary> /// <param name="request">The request.</param> @@ -1167,6 +1233,11 @@ namespace MediaBrowser.Api.Playback /// <returns>StreamState.</returns> protected async Task<StreamState> GetState(StreamRequest request, CancellationToken cancellationToken) { + if (!string.IsNullOrWhiteSpace(request.Params)) + { + ParseParams(request); + } + if (request.ThrowDebugError) { throw new InvalidOperationException("You asked for a debug error, you got one."); diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index 78682d54a..a73a8f0d9 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -61,15 +61,11 @@ namespace MediaBrowser.Api.Playback public bool Static { get; set; } /// <summary> - /// This is an xbox 360 param that is used with dlna. If true the item's image should be returned instead of audio or video. - /// No need to put this in api docs since it's dlna only - /// </summary> - public bool AlbumArt { get; set; } - - /// <summary> /// For testing purposes /// </summary> public bool ThrowDebugError { get; set; } + + public string Params { get; set; } } public class VideoStreamRequest : StreamRequest diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 9ddefdd05..8e94d2c83 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -189,7 +189,7 @@ namespace MediaBrowser.Api { throw new ArgumentNullException("xmlSerializer"); } - + _xmlSerializer = xmlSerializer; _userManager = userManager; _dtoService = dtoService; diff --git a/MediaBrowser.Controller/Entities/AdultVideo.cs b/MediaBrowser.Controller/Entities/AdultVideo.cs index fc7632152..9791f7cf7 100644 --- a/MediaBrowser.Controller/Entities/AdultVideo.cs +++ b/MediaBrowser.Controller/Entities/AdultVideo.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using MediaBrowser.Controller.Providers; namespace MediaBrowser.Controller.Entities { @@ -22,5 +23,26 @@ namespace MediaBrowser.Controller.Entities { Taglines = new List<string>(); } + + public override bool BeforeMetadataRefresh() + { + var hasChanges = base.BeforeMetadataRefresh(); + + if (!ProductionYear.HasValue) + { + int? yearInName = null; + string name; + + NameParser.ParseName(Name, out name, out yearInName); + + if (yearInName.HasValue) + { + ProductionYear = yearInName; + hasChanges = true; + } + } + + return hasChanges; + } } } diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs index 5cabe1cfe..362096b5e 100644 --- a/MediaBrowser.Controller/Entities/AggregateFolder.cs +++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs @@ -66,7 +66,7 @@ namespace MediaBrowser.Controller.Entities { var path = ContainingFolderPath; - var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager) + var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager, directoryService) { FileInfo = new DirectoryInfo(path), Path = path, diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 8dcf08642..415b49f80 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -472,7 +472,7 @@ namespace MediaBrowser.Controller.Entities /// Loads local trailers from the file system /// </summary> /// <returns>List{Video}.</returns> - private IEnumerable<Trailer> LoadLocalTrailers(List<FileSystemInfo> fileSystemChildren) + private IEnumerable<Trailer> LoadLocalTrailers(List<FileSystemInfo> fileSystemChildren, IDirectoryService directoryService) { var files = fileSystemChildren.OfType<DirectoryInfo>() .Where(i => string.Equals(i.Name, TrailerFolderName, StringComparison.OrdinalIgnoreCase)) @@ -484,7 +484,7 @@ namespace MediaBrowser.Controller.Entities .Where(i => System.IO.Path.GetFileNameWithoutExtension(i.Name).EndsWith(XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) && !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase)) ); - return LibraryManager.ResolvePaths<Trailer>(files, null).Select(video => + return LibraryManager.ResolvePaths<Trailer>(files, directoryService, null).Select(video => { // Try to retrieve it from the db. If we don't find it, use the resolved version var dbItem = LibraryManager.GetItemById(video.Id) as Trailer; @@ -504,7 +504,7 @@ namespace MediaBrowser.Controller.Entities /// Loads the theme songs. /// </summary> /// <returns>List{Audio.Audio}.</returns> - private IEnumerable<Audio.Audio> LoadThemeSongs(List<FileSystemInfo> fileSystemChildren) + private IEnumerable<Audio.Audio> LoadThemeSongs(List<FileSystemInfo> fileSystemChildren, IDirectoryService directoryService) { var files = fileSystemChildren.OfType<DirectoryInfo>() .Where(i => string.Equals(i.Name, ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase)) @@ -516,7 +516,7 @@ namespace MediaBrowser.Controller.Entities .Where(i => string.Equals(System.IO.Path.GetFileNameWithoutExtension(i.Name), ThemeSongFilename, StringComparison.OrdinalIgnoreCase)) ); - return LibraryManager.ResolvePaths<Audio.Audio>(files, null).Select(audio => + return LibraryManager.ResolvePaths<Audio.Audio>(files, directoryService, null).Select(audio => { // Try to retrieve it from the db. If we don't find it, use the resolved version var dbItem = LibraryManager.GetItemById(audio.Id) as Audio.Audio; @@ -536,13 +536,13 @@ namespace MediaBrowser.Controller.Entities /// Loads the video backdrops. /// </summary> /// <returns>List{Video}.</returns> - private IEnumerable<Video> LoadThemeVideos(IEnumerable<FileSystemInfo> fileSystemChildren) + private IEnumerable<Video> LoadThemeVideos(IEnumerable<FileSystemInfo> fileSystemChildren, IDirectoryService directoryService) { var files = fileSystemChildren.OfType<DirectoryInfo>() .Where(i => string.Equals(i.Name, ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase)) .SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly)); - return LibraryManager.ResolvePaths<Video>(files, null).Select(item => + return LibraryManager.ResolvePaths<Video>(files, directoryService, null).Select(item => { // Try to retrieve it from the db. If we don't find it, use the resolved version var dbItem = LibraryManager.GetItemById(item.Id) as Video; @@ -579,15 +579,22 @@ namespace MediaBrowser.Controller.Entities { options.DirectoryService = options.DirectoryService ?? new DirectoryService(Logger); - var files = locationType == LocationType.FileSystem || locationType == LocationType.Offline ? - GetFileSystemChildren(options.DirectoryService).ToList() : - new List<FileSystemInfo>(); + try + { + var files = locationType == LocationType.FileSystem || locationType == LocationType.Offline ? + GetFileSystemChildren(options.DirectoryService).ToList() : + new List<FileSystemInfo>(); - var ownedItemsChanged = await RefreshedOwnedItems(options, files, cancellationToken).ConfigureAwait(false); + var ownedItemsChanged = await RefreshedOwnedItems(options, files, cancellationToken).ConfigureAwait(false); - if (ownedItemsChanged) + if (ownedItemsChanged) + { + requiresSave = true; + } + } + catch (Exception ex) { - requiresSave = true; + Logger.ErrorException("Error refreshing owned items for {0}", ex, Path ?? Name); } } @@ -650,7 +657,7 @@ namespace MediaBrowser.Controller.Entities private async Task<bool> RefreshLocalTrailers(IHasTrailers item, MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { - var newItems = LoadLocalTrailers(fileSystemChildren).ToList(); + var newItems = LoadLocalTrailers(fileSystemChildren, options.DirectoryService).ToList(); var newItemIds = newItems.Select(i => i.Id).ToList(); var itemsChanged = !item.LocalTrailerIds.SequenceEqual(newItemIds); @@ -666,7 +673,7 @@ namespace MediaBrowser.Controller.Entities private async Task<bool> RefreshThemeVideos(IHasThemeMedia item, MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { - var newThemeVideos = LoadThemeVideos(fileSystemChildren).ToList(); + var newThemeVideos = LoadThemeVideos(fileSystemChildren, options.DirectoryService).ToList(); var newThemeVideoIds = newThemeVideos.Select(i => i.Id).ToList(); @@ -686,7 +693,7 @@ namespace MediaBrowser.Controller.Entities /// </summary> private async Task<bool> RefreshThemeSongs(IHasThemeMedia item, MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { - var newThemeSongs = LoadThemeSongs(fileSystemChildren).ToList(); + var newThemeSongs = LoadThemeSongs(fileSystemChildren, options.DirectoryService).ToList(); var newThemeSongIds = newThemeSongs.Select(i => i.Id).ToList(); var themeSongsChanged = !item.ThemeSongIds.SequenceEqual(newThemeSongIds); @@ -1422,20 +1429,19 @@ namespace MediaBrowser.Controller.Entities } /// <summary> - /// This is called before any metadata refresh and returns ItemUpdateType indictating if changes were made, and what. + /// This is called before any metadata refresh and returns true or false indicating if changes were made /// </summary> - /// <returns>ItemUpdateType.</returns> - public virtual ItemUpdateType BeforeMetadataRefresh() + public virtual bool BeforeMetadataRefresh() { - var updateType = ItemUpdateType.None; + var hasChanges = false; if (string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(Path)) { Name = System.IO.Path.GetFileNameWithoutExtension(Path); - updateType = updateType | ItemUpdateType.MetadataEdit; + hasChanges = true; } - return updateType; + return hasChanges; } } } diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 416796b69..88219bcbf 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -70,7 +70,7 @@ namespace MediaBrowser.Controller.Entities { var path = ContainingFolderPath; - var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager) + var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager, directoryService) { FileInfo = new DirectoryInfo(path), Path = path, diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index cb14ed099..b718864d2 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -681,7 +681,7 @@ namespace MediaBrowser.Controller.Entities /// <returns>IEnumerable{BaseItem}.</returns> protected virtual IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService) { - return LibraryManager.ResolvePaths<BaseItem>(GetFileSystemChildren(directoryService), this); + return LibraryManager.ResolvePaths<BaseItem>(GetFileSystemChildren(directoryService), directoryService, this); } /// <summary> diff --git a/MediaBrowser.Controller/Entities/IHasMetadata.cs b/MediaBrowser.Controller/Entities/IHasMetadata.cs index 0285b6749..7182d086a 100644 --- a/MediaBrowser.Controller/Entities/IHasMetadata.cs +++ b/MediaBrowser.Controller/Entities/IHasMetadata.cs @@ -51,9 +51,9 @@ namespace MediaBrowser.Controller.Entities Task UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken); /// <summary> - /// This is called before any metadata refresh and returns ItemUpdateType indictating if changes were made, and what. + /// This is called before any metadata refresh and returns true or false indicating if changes were made /// </summary> - /// <returns>ItemUpdateType.</returns> - ItemUpdateType BeforeMetadataRefresh(); + /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> + bool BeforeMetadataRefresh(); } } diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index 8eba21df0..846a2ae7b 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using System; @@ -117,7 +118,7 @@ namespace MediaBrowser.Controller.Entities.Movies private async Task<bool> RefreshSpecialFeatures(MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { - var newItems = LoadSpecialFeatures(fileSystemChildren).ToList(); + var newItems = LoadSpecialFeatures(fileSystemChildren, options.DirectoryService).ToList(); var newItemIds = newItems.Select(i => i.Id).ToList(); var itemsChanged = !SpecialFeatureIds.SequenceEqual(newItemIds); @@ -135,13 +136,13 @@ namespace MediaBrowser.Controller.Entities.Movies /// Loads the special features. /// </summary> /// <returns>IEnumerable{Video}.</returns> - private IEnumerable<Video> LoadSpecialFeatures(IEnumerable<FileSystemInfo> fileSystemChildren) + private IEnumerable<Video> LoadSpecialFeatures(IEnumerable<FileSystemInfo> fileSystemChildren, IDirectoryService directoryService) { var files = fileSystemChildren.OfType<DirectoryInfo>() .Where(i => string.Equals(i.Name, "extras", StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, "specials", StringComparison.OrdinalIgnoreCase)) .SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly)); - return LibraryManager.ResolvePaths<Video>(files, null).Select(video => + return LibraryManager.ResolvePaths<Video>(files, directoryService, null).Select(video => { // Try to retrieve it from the db. If we don't find it, use the resolved version var dbItem = LibraryManager.GetItemById(video.Id) as Video; @@ -166,5 +167,26 @@ namespace MediaBrowser.Controller.Entities.Movies { return GetItemLookupInfo<MovieInfo>(); } + + public override bool BeforeMetadataRefresh() + { + var hasChanges = base.BeforeMetadataRefresh(); + + if (!ProductionYear.HasValue) + { + int? yearInName = null; + string name; + + NameParser.ParseName(Name, out name, out yearInName); + + if (yearInName.HasValue) + { + ProductionYear = yearInName; + hasChanges = true; + } + } + + return hasChanges; + } } } diff --git a/MediaBrowser.Controller/Entities/Photo.cs b/MediaBrowser.Controller/Entities/Photo.cs new file mode 100644 index 000000000..96995c315 --- /dev/null +++ b/MediaBrowser.Controller/Entities/Photo.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Entities +{ + public class Photo : BaseItem, IHasTags, IHasTaglines + { + public List<string> Tags { get; set; } + public List<string> Taglines { get; set; } + + public Photo() + { + Tags = new List<string>(); + Taglines = new List<string>(); + } + + public override string MediaType + { + get + { + return Model.Entities.MediaType.Photo; + } + } + } +} diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index daff3dd6c..1240bbb9f 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -263,32 +263,32 @@ namespace MediaBrowser.Controller.Entities.TV return id; } - public override ItemUpdateType BeforeMetadataRefresh() + public override bool BeforeMetadataRefresh() { - var updateType = base.BeforeMetadataRefresh(); + var hasChanges = base.BeforeMetadataRefresh(); var locationType = LocationType; if (locationType == LocationType.FileSystem || locationType == LocationType.Offline) { if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path)) { - IndexNumber = IndexNumber ?? TVUtils.GetEpisodeNumberFromFile(Path, Parent is Season); + IndexNumber = TVUtils.GetEpisodeNumberFromFile(Path, Parent is Season); // If a change was made record it if (IndexNumber.HasValue) { - updateType = updateType | ItemUpdateType.MetadataImport; + hasChanges = true; } } if (!IndexNumberEnd.HasValue && !string.IsNullOrEmpty(Path)) { - IndexNumberEnd = IndexNumberEnd ?? TVUtils.GetEndingEpisodeNumberFromFile(Path); + IndexNumberEnd = TVUtils.GetEndingEpisodeNumberFromFile(Path); // If a change was made record it if (IndexNumberEnd.HasValue) { - updateType = updateType | ItemUpdateType.MetadataImport; + hasChanges = true; } } } @@ -302,14 +302,25 @@ namespace MediaBrowser.Controller.Entities.TV ParentIndexNumber = season.IndexNumber; } + if (!ParentIndexNumber.HasValue && !string.IsNullOrEmpty(Path)) + { + ParentIndexNumber = TVUtils.GetSeasonNumberFromPath(Path); + + // If a change was made record it + if (ParentIndexNumber.HasValue) + { + hasChanges = true; + } + } + // If a change was made record it if (ParentIndexNumber.HasValue) { - updateType = updateType | ItemUpdateType.MetadataImport; + hasChanges = true; } } - return updateType; + return hasChanges; } } } diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 830ccb8a2..2847c397e 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -254,12 +254,12 @@ namespace MediaBrowser.Controller.Entities.TV } /// <summary> - /// This is called before any metadata refresh and returns ItemUpdateType indictating if changes were made, and what. + /// This is called before any metadata refresh and returns true or false indicating if changes were made /// </summary> - /// <returns>ItemUpdateType.</returns> - public override ItemUpdateType BeforeMetadataRefresh() + /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> + public override bool BeforeMetadataRefresh() { - var updateType = base.BeforeMetadataRefresh(); + var hasChanges = base.BeforeMetadataRefresh(); var locationType = LocationType; @@ -272,12 +272,12 @@ namespace MediaBrowser.Controller.Entities.TV // If a change was made record it if (IndexNumber.HasValue) { - updateType = updateType | ItemUpdateType.MetadataImport; + hasChanges = true; } } } - return updateType; + return hasChanges; } } } diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 0e07654d6..4696c8a0f 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -228,5 +228,26 @@ namespace MediaBrowser.Controller.Entities.TV { return GetItemLookupInfo<SeriesInfo>(); } + + public override bool BeforeMetadataRefresh() + { + var hasChanges = base.BeforeMetadataRefresh(); + + if (!ProductionYear.HasValue) + { + int? yearInName = null; + string name; + + NameParser.ParseName(Name, out name, out yearInName); + + if (yearInName.HasValue) + { + ProductionYear = yearInName; + hasChanges = true; + } + } + + return hasChanges; + } } } diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index dc3d4c384..9a8d3e7f4 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Providers; using System.Collections.Generic; using System.Linq; @@ -20,17 +19,17 @@ namespace MediaBrowser.Controller.Entities return base.GetNonCachedChildren(directoryService).Concat(LibraryManager.RootFolder.VirtualChildren); } - public override ItemUpdateType BeforeMetadataRefresh() + public override bool BeforeMetadataRefresh() { - var updateType = base.BeforeMetadataRefresh(); + var hasChanges = base.BeforeMetadataRefresh(); if (string.Equals("default", Name, System.StringComparison.OrdinalIgnoreCase)) { Name = "Default Media Library"; - updateType = updateType | ItemUpdateType.MetadataEdit; + hasChanges = true; } - return updateType; + return hasChanges; } } } diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index e778b38bd..e16683d6d 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -192,7 +192,7 @@ namespace MediaBrowser.Controller.Entities /// <returns>Task{System.Boolean}.</returns> private async Task<bool> RefreshAdditionalParts(MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { - var newItems = LoadAdditionalParts(fileSystemChildren).ToList(); + var newItems = LoadAdditionalParts(fileSystemChildren, options.DirectoryService).ToList(); var newItemIds = newItems.Select(i => i.Id).ToList(); @@ -211,7 +211,7 @@ namespace MediaBrowser.Controller.Entities /// Loads the additional parts. /// </summary> /// <returns>IEnumerable{Video}.</returns> - private IEnumerable<Video> LoadAdditionalParts(IEnumerable<FileSystemInfo> fileSystemChildren) + private IEnumerable<Video> LoadAdditionalParts(IEnumerable<FileSystemInfo> fileSystemChildren, IDirectoryService directoryService) { IEnumerable<FileSystemInfo> files; @@ -242,7 +242,7 @@ namespace MediaBrowser.Controller.Entities }); } - return LibraryManager.ResolvePaths<Video>(files, null).Select(video => + return LibraryManager.ResolvePaths<Video>(files, directoryService, null).Select(video => { // Try to retrieve it from the db. If we don't find it, use the resolved version var dbItem = LibraryManager.GetItemById(video.Id) as Video; diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 7c803e651..e5e7db13d 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Entities; @@ -27,19 +28,29 @@ namespace MediaBrowser.Controller.Library /// Resolves a path into a BaseItem /// </summary> /// <param name="fileInfo">The file info.</param> + /// <param name="directoryService">The directory service.</param> /// <param name="parent">The parent.</param> /// <returns>BaseItem.</returns> /// <exception cref="System.ArgumentNullException"></exception> - BaseItem ResolvePath(FileSystemInfo fileInfo, Folder parent = null); + BaseItem ResolvePath(FileSystemInfo fileInfo, IDirectoryService directoryService, Folder parent = null); /// <summary> + /// Resolves the path. + /// </summary> + /// <param name="fileInfo">The file information.</param> + /// <param name="parent">The parent.</param> + /// <returns>BaseItem.</returns> + BaseItem ResolvePath(FileSystemInfo fileInfo, Folder parent = null); + + /// <summary> /// Resolves a set of files into a list of BaseItem /// </summary> /// <typeparam name="T"></typeparam> /// <param name="files">The files.</param> + /// <param name="directoryService">The directory service.</param> /// <param name="parent">The parent.</param> /// <returns>List{``0}.</returns> - List<T> ResolvePaths<T>(IEnumerable<FileSystemInfo> files, Folder parent) + List<T> ResolvePaths<T>(IEnumerable<FileSystemInfo> files, IDirectoryService directoryService, Folder parent) where T : BaseItem; /// <summary> diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs index d84e7aa8c..c1fcdc9fe 100644 --- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs +++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; using System; using System.Collections.Generic; using System.IO; @@ -18,15 +19,18 @@ namespace MediaBrowser.Controller.Library private readonly IServerApplicationPaths _appPaths; private readonly ILibraryManager _libraryManager; + public IDirectoryService DirectoryService { get; private set; } + /// <summary> /// Initializes a new instance of the <see cref="ItemResolveArgs" /> class. /// </summary> /// <param name="appPaths">The app paths.</param> /// <param name="libraryManager">The library manager.</param> - public ItemResolveArgs(IServerApplicationPaths appPaths, ILibraryManager libraryManager) + public ItemResolveArgs(IServerApplicationPaths appPaths, ILibraryManager libraryManager, IDirectoryService directoryService) { _appPaths = appPaths; _libraryManager = libraryManager; + DirectoryService = directoryService; } /// <summary> diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 18ac01c8b..b62492bfe 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -109,6 +109,7 @@ <Compile Include="Entities\LinkedChild.cs" /> <Compile Include="Entities\MusicVideo.cs" /> <Compile Include="Entities\IHasAwards.cs" /> + <Compile Include="Entities\Photo.cs" /> <Compile Include="FileOrganization\IFileOrganizationService.cs" /> <Compile Include="Library\ILibraryPostScanTask.cs" /> <Compile Include="Library\IMetadataSaver.cs" /> diff --git a/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs b/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs index 23fda2bfa..6b94547bb 100644 --- a/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs +++ b/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs @@ -24,18 +24,9 @@ namespace MediaBrowser.Controller.Providers /// Gets the images. /// </summary> /// <param name="item">The item.</param> - /// <param name="imageType">Type of the image.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns> - Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken); - - /// <summary> - /// Gets the images. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns> - Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken); + Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken); /// <summary> /// Gets the image response. diff --git a/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs b/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs index 079571ee9..42b637140 100644 --- a/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs +++ b/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs @@ -77,21 +77,21 @@ namespace MediaBrowser.Controller.Resolvers /// <summary> /// The audio file extensions /// </summary> - public static readonly string[] AudioFileExtensions = new[] - { - ".mp3", - ".flac", - ".wma", - ".aac", - ".acc", - ".m4a", - ".m4b", - ".wav", - ".ape", - ".ogg", - ".oga" - - }; + public static readonly string[] AudioFileExtensions = + { + ".mp3", + ".flac", + ".wma", + ".aac", + ".acc", + ".m4a", + ".m4b", + ".wav", + ".ape", + ".ogg", + ".oga" + + }; private static readonly Dictionary<string, string> AudioFileExtensionsDictionary = AudioFileExtensions.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); diff --git a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs index b7672a6a7..e82c6a1b9 100644 --- a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs +++ b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs @@ -47,14 +47,7 @@ namespace MediaBrowser.Providers.BoxSets }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) - { - var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); - - return images.Where(i => i.Type == imageType); - } - - public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken) { var tmdbId = item.GetProviderId(MetadataProviders.Tmdb); diff --git a/MediaBrowser.Providers/GameGenres/GameGenreImageProvider.cs b/MediaBrowser.Providers/GameGenres/GameGenreImageProvider.cs index 85aa9f716..12f14676f 100644 --- a/MediaBrowser.Providers/GameGenres/GameGenreImageProvider.cs +++ b/MediaBrowser.Providers/GameGenres/GameGenreImageProvider.cs @@ -54,12 +54,7 @@ namespace MediaBrowser.Providers.GameGenres }; } - public Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) - { - return GetImages(item, imageType == ImageType.Primary, imageType == ImageType.Thumb, cancellationToken); - } - - public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + public Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken) { return GetImages(item, true, true, cancellationToken); } diff --git a/MediaBrowser.Providers/Genres/GenreImageProvider.cs b/MediaBrowser.Providers/Genres/GenreImageProvider.cs index 007eeab7b..4d19bb91e 100644 --- a/MediaBrowser.Providers/Genres/GenreImageProvider.cs +++ b/MediaBrowser.Providers/Genres/GenreImageProvider.cs @@ -55,12 +55,7 @@ namespace MediaBrowser.Providers.Genres }; } - public Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) - { - return GetImages(item, imageType == ImageType.Primary, imageType == ImageType.Thumb, cancellationToken); - } - - public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + public Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken) { return GetImages(item, true, true, cancellationToken); } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index beece997d..8b085a05a 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -106,7 +106,10 @@ namespace MediaBrowser.Providers.Manager if (providers.Count > 0 || !refreshResult.DateLastMetadataRefresh.HasValue) { - updateType = updateType | item.BeforeMetadataRefresh(); + if (item.BeforeMetadataRefresh()) + { + updateType = updateType | ItemUpdateType.MetadataImport; + } } if (providers.Count > 0) @@ -416,7 +419,13 @@ namespace MediaBrowser.Providers.Manager // Copy new provider id's that may have been obtained foreach (var providerId in source.ProviderIds) { - lookupInfo.ProviderIds[providerId.Key] = providerId.Value; + var key = providerId.Key; + + // Don't replace existing Id's. + if (!lookupInfo.ProviderIds.ContainsKey(key)) + { + lookupInfo.ProviderIds[key] = providerId.Value; + } } } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 44abeac43..43d4f20c1 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -237,32 +237,27 @@ namespace MediaBrowser.Providers.Manager /// </summary> /// <param name="item">The item.</param> /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="i">The i.</param> + /// <param name="provider">The provider.</param> /// <param name="preferredLanguage">The preferred language.</param> /// <param name="type">The type.</param> /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns> - private async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken, IRemoteImageProvider i, string preferredLanguage, ImageType? type = null) + private async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken, IRemoteImageProvider provider, string preferredLanguage, ImageType? type = null) { try { + var result = await provider.GetImages(item, cancellationToken).ConfigureAwait(false); + if (type.HasValue) { - var result = await i.GetImages(item, type.Value, cancellationToken).ConfigureAwait(false); - - return string.IsNullOrEmpty(preferredLanguage) ? result : - FilterImages(result, preferredLanguage); + result = result.Where(i => i.Type == type.Value); } - else - { - var result = await i.GetAllImages(item, cancellationToken).ConfigureAwait(false); - return string.IsNullOrEmpty(preferredLanguage) ? result : - FilterImages(result, preferredLanguage); - } + return string.IsNullOrEmpty(preferredLanguage) ? result : + FilterImages(result, preferredLanguage); } catch (Exception ex) { - _logger.ErrorException("{0} failed in GetImageInfos for type {1}", ex, i.GetType().Name, item.GetType().Name); + _logger.ErrorException("{0} failed in GetImageInfos for type {1}", ex, provider.GetType().Name, item.GetType().Name); return new List<RemoteImageInfo>(); } } diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs index 6e994c9f2..025945461 100644 --- a/MediaBrowser.Providers/Manager/ProviderUtils.cs +++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs @@ -157,7 +157,13 @@ namespace MediaBrowser.Providers.Manager foreach (var id in source.ProviderIds) { - target.ProviderIds[id.Key] = id.Value; + var key = id.Key; + + // Don't replace existing Id's. + if (!target.ProviderIds.ContainsKey(key)) + { + target.ProviderIds[key] = id.Value; + } } MergeAlbumArtist(source, target, lockedFields, replaceData); diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 5cf4b2591..85f988344 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -151,6 +151,11 @@ <Compile Include="People\PersonMetadataService.cs" /> <Compile Include="People\PersonXmlProvider.cs" /> <Compile Include="People\MovieDbPersonProvider.cs" /> + <Compile Include="Photos\ExifReader.cs" /> + <Compile Include="Photos\ExifTags.cs" /> + <Compile Include="Photos\PhotoHelper.cs" /> + <Compile Include="Photos\PhotoMetadataService.cs" /> + <Compile Include="Photos\PhotoProvider.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Manager\ProviderUtils.cs" /> <Compile Include="Savers\AlbumXmlSaver.cs" /> diff --git a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs index 26395e9cc..b11569495 100644 --- a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs +++ b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs @@ -78,14 +78,7 @@ namespace MediaBrowser.Providers.Movies }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) - { - var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); - - return images.Where(i => i.Type == imageType); - } - - public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken) { var baseItem = (BaseItem)item; var list = new List<RemoteImageInfo>(); diff --git a/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs b/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs index 8a38e294c..9828a20bc 100644 --- a/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs @@ -58,14 +58,7 @@ namespace MediaBrowser.Providers.Movies }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) - { - var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); - - return images.Where(i => i.Type == imageType); - } - - public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken) { var list = new List<RemoteImageInfo>(); diff --git a/MediaBrowser.Providers/Movies/MovieDbSearch.cs b/MediaBrowser.Providers/Movies/MovieDbSearch.cs index 5a2b865f5..5f5ece4a8 100644 --- a/MediaBrowser.Providers/Movies/MovieDbSearch.cs +++ b/MediaBrowser.Providers/Movies/MovieDbSearch.cs @@ -46,11 +46,8 @@ namespace MediaBrowser.Providers.Movies private async Task<string> FindId(ItemLookupInfo idInfo, string searchType, CancellationToken cancellationToken) { - int? yearInName; var name = idInfo.Name; - NameParser.ParseName(name, out name, out yearInName); - - var year = idInfo.Year ?? yearInName; + var year = idInfo.Year; _logger.Info("MovieDbProvider: Finding id for item: " + name); var language = idInfo.MetadataLanguage.ToLower(); @@ -266,5 +263,24 @@ namespace MediaBrowser.Providers.Movies public int total_results { get; set; } } + public class TvResult + { + public string backdrop_path { get; set; } + public int id { get; set; } + public string original_name { get; set; } + public string first_air_date { get; set; } + public string poster_path { get; set; } + public double popularity { get; set; } + public string name { get; set; } + public double vote_average { get; set; } + public int vote_count { get; set; } + } + + public class ExternalIdLookupResult + { + public List<object> movie_results { get; set; } + public List<object> person_results { get; set; } + public List<TvResult> tv_results { get; set; } + } } } diff --git a/MediaBrowser.Providers/Music/AudioDbAlbumImageProvider.cs b/MediaBrowser.Providers/Music/AudioDbAlbumImageProvider.cs index a8f3b8a6d..3de85593a 100644 --- a/MediaBrowser.Providers/Music/AudioDbAlbumImageProvider.cs +++ b/MediaBrowser.Providers/Music/AudioDbAlbumImageProvider.cs @@ -35,14 +35,7 @@ namespace MediaBrowser.Providers.Music }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) - { - var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); - - return images.Where(i => i.Type == imageType); - } - - public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken) { var id = item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup); diff --git a/MediaBrowser.Providers/Music/AudioDbArtistImageProvider.cs b/MediaBrowser.Providers/Music/AudioDbArtistImageProvider.cs index 79238fd51..10a197b37 100644 --- a/MediaBrowser.Providers/Music/AudioDbArtistImageProvider.cs +++ b/MediaBrowser.Providers/Music/AudioDbArtistImageProvider.cs @@ -37,14 +37,7 @@ namespace MediaBrowser.Providers.Music }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) - { - var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); - - return images.Where(i => i.Type == imageType); - } - - public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken) { var id = item.GetProviderId(MetadataProviders.MusicBrainzArtist); diff --git a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs index 8479b0c6e..93db4f5b4 100644 --- a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs @@ -57,14 +57,7 @@ namespace MediaBrowser.Providers.Music }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) - { - var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); - - return images.Where(i => i.Type == imageType); - } - - public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken) { var album = (MusicAlbum)item; diff --git a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs index 9b32ff4f2..d96178059 100644 --- a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs @@ -69,14 +69,7 @@ namespace MediaBrowser.Providers.Music }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) - { - var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); - - return images.Where(i => i.Type == imageType); - } - - public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken) { var artist = (MusicArtist)item; diff --git a/MediaBrowser.Providers/Music/LastFmImageProvider.cs b/MediaBrowser.Providers/Music/LastFmImageProvider.cs index 701e74da0..b55a97351 100644 --- a/MediaBrowser.Providers/Music/LastFmImageProvider.cs +++ b/MediaBrowser.Providers/Music/LastFmImageProvider.cs @@ -48,14 +48,7 @@ namespace MediaBrowser.Providers.Music }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) - { - var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); - - return images.Where(i => i.Type == imageType); - } - - public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + public Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken) { var list = new List<RemoteImageInfo>(); diff --git a/MediaBrowser.Providers/MusicGenres/MusicGenreImageProvider.cs b/MediaBrowser.Providers/MusicGenres/MusicGenreImageProvider.cs index 0867156e3..6c239ab30 100644 --- a/MediaBrowser.Providers/MusicGenres/MusicGenreImageProvider.cs +++ b/MediaBrowser.Providers/MusicGenres/MusicGenreImageProvider.cs @@ -55,12 +55,7 @@ namespace MediaBrowser.Providers.MusicGenres }; } - public Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) - { - return GetImages(item, imageType == ImageType.Primary, imageType == ImageType.Thumb, cancellationToken); - } - - public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + public Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken) { return GetImages(item, true, true, cancellationToken); } diff --git a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs index db5f1b8ce..2e85e5401 100644 --- a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs +++ b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs @@ -7,8 +7,6 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Providers.Manager; using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.MusicGenres { diff --git a/MediaBrowser.Providers/People/MovieDbPersonImageProvider.cs b/MediaBrowser.Providers/People/MovieDbPersonImageProvider.cs index 3f8278bda..2ac9fdfa0 100644 --- a/MediaBrowser.Providers/People/MovieDbPersonImageProvider.cs +++ b/MediaBrowser.Providers/People/MovieDbPersonImageProvider.cs @@ -50,14 +50,7 @@ namespace MediaBrowser.Providers.People }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) - { - var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); - - return images.Where(i => i.Type == imageType); - } - - public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken) { var person = (Person)item; var id = person.GetProviderId(MetadataProviders.Tmdb); diff --git a/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs b/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs index 769f07788..63d054664 100644 --- a/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs +++ b/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs @@ -54,14 +54,7 @@ namespace MediaBrowser.Providers.People }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) - { - var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); - - return images.Where(i => i.Type == imageType); - } - - public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + public Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken) { var seriesWithPerson = _library.RootFolder .RecursiveChildren diff --git a/MediaBrowser.Providers/Photos/ExifReader.cs b/MediaBrowser.Providers/Photos/ExifReader.cs new file mode 100644 index 000000000..8526a7f2a --- /dev/null +++ b/MediaBrowser.Providers/Photos/ExifReader.cs @@ -0,0 +1,613 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; + +namespace MediaBrowser.Providers.Photos +{ + /// <summary> + /// A class for reading Exif data from a JPEG file. The file will be open for reading for as long as the class exists. + /// <seealso cref="http://gvsoft.homedns.org/exif/Exif-explanation.html"/> + /// </summary> + public class ExifReader : IDisposable + { + private readonly FileStream fileStream = null; + private readonly BinaryReader reader = null; + + /// <summary> + /// The catalogue of tag ids and their absolute offsets within the + /// file + /// </summary> + private Dictionary<ushort, long> catalogue; + + /// <summary> + /// Indicates whether to read data using big or little endian byte aligns + /// </summary> + private bool isLittleEndian; + + /// <summary> + /// The position in the filestream at which the TIFF header starts + /// </summary> + private long tiffHeaderStart; + + public ExifReader(string fileName) + { + // JPEG encoding uses big endian (i.e. Motorola) byte aligns. The TIFF encoding + // found later in the document will specify the byte aligns used for the + // rest of the document. + isLittleEndian = false; + + try + { + // Open the file in a stream + fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + reader = new BinaryReader(fileStream); + + // Make sure the file's a JPEG. + if (ReadUShort() != 0xFFD8) + throw new Exception("File is not a valid JPEG"); + + // Scan to the start of the Exif content + ReadToExifStart(); + + // Create an index of all Exif tags found within the document + CreateTagIndex(); + } + catch (Exception) + { + // If instantiation fails, make sure there's no mess left behind + Dispose(); + + throw; + } + } + + #region TIFF methods + + /// <summary> + /// Returns the length (in bytes) per component of the specified TIFF data type + /// </summary> + /// <returns></returns> + private byte GetTIFFFieldLength(ushort tiffDataType) + { + switch (tiffDataType) + { + case 1: + case 2: + case 6: + return 1; + case 3: + case 8: + return 2; + case 4: + case 7: + case 9: + case 11: + return 4; + case 5: + case 10: + case 12: + return 8; + default: + throw new Exception(string.Format("Unknown TIFF datatype: {0}", tiffDataType)); + } + } + + #endregion + + #region Methods for reading data directly from the filestream + + /// <summary> + /// Gets a 2 byte unsigned integer from the file + /// </summary> + /// <returns></returns> + private ushort ReadUShort() + { + return ToUShort(ReadBytes(2)); + } + + /// <summary> + /// Gets a 4 byte unsigned integer from the file + /// </summary> + /// <returns></returns> + private uint ReadUint() + { + return ToUint(ReadBytes(4)); + } + + private string ReadString(int chars) + { + return Encoding.ASCII.GetString(ReadBytes(chars)); + } + + private byte[] ReadBytes(int byteCount) + { + return reader.ReadBytes(byteCount); + } + + /// <summary> + /// Reads some bytes from the specified TIFF offset + /// </summary> + /// <param name="tiffOffset"></param> + /// <param name="byteCount"></param> + /// <returns></returns> + private byte[] ReadBytes(ushort tiffOffset, int byteCount) + { + // Keep the current file offset + long originalOffset = fileStream.Position; + + // Move to the TIFF offset and retrieve the data + fileStream.Seek(tiffOffset + tiffHeaderStart, SeekOrigin.Begin); + + byte[] data = reader.ReadBytes(byteCount); + + // Restore the file offset + fileStream.Position = originalOffset; + + return data; + } + + #endregion + + #region Data conversion methods for interpreting datatypes from a byte array + + /// <summary> + /// Converts 2 bytes to a ushort using the current byte aligns + /// </summary> + /// <returns></returns> + private ushort ToUShort(byte[] data) + { + if (isLittleEndian != BitConverter.IsLittleEndian) + Array.Reverse(data); + + return BitConverter.ToUInt16(data, 0); + } + + /// <summary> + /// Converts 8 bytes to an unsigned rational using the current byte aligns. + /// </summary> + /// <param name="data"></param> + /// <returns></returns> + /// <seealso cref="ToRational"/> + private double ToURational(byte[] data) + { + var numeratorData = new byte[4]; + var denominatorData = new byte[4]; + + Array.Copy(data, numeratorData, 4); + Array.Copy(data, 4, denominatorData, 0, 4); + + uint numerator = ToUint(numeratorData); + uint denominator = ToUint(denominatorData); + + return numerator / (double)denominator; + } + + /// <summary> + /// Converts 8 bytes to a signed rational using the current byte aligns. + /// </summary> + /// <remarks> + /// A TIFF rational contains 2 4-byte integers, the first of which is + /// the numerator, and the second of which is the denominator. + /// </remarks> + /// <param name="data"></param> + /// <returns></returns> + private double ToRational(byte[] data) + { + var numeratorData = new byte[4]; + var denominatorData = new byte[4]; + + Array.Copy(data, numeratorData, 4); + Array.Copy(data, 4, denominatorData, 0, 4); + + int numerator = ToInt(numeratorData); + int denominator = ToInt(denominatorData); + + return numerator / (double)denominator; + } + + /// <summary> + /// Converts 4 bytes to a uint using the current byte aligns + /// </summary> + /// <returns></returns> + private uint ToUint(byte[] data) + { + if (isLittleEndian != BitConverter.IsLittleEndian) + Array.Reverse(data); + + return BitConverter.ToUInt32(data, 0); + } + + /// <summary> + /// Converts 4 bytes to an int using the current byte aligns + /// </summary> + /// <returns></returns> + private int ToInt(byte[] data) + { + if (isLittleEndian != BitConverter.IsLittleEndian) + Array.Reverse(data); + + return BitConverter.ToInt32(data, 0); + } + + private double ToDouble(byte[] data) + { + if (isLittleEndian != BitConverter.IsLittleEndian) + Array.Reverse(data); + + return BitConverter.ToDouble(data, 0); + } + + private float ToSingle(byte[] data) + { + if (isLittleEndian != BitConverter.IsLittleEndian) + Array.Reverse(data); + + return BitConverter.ToSingle(data, 0); + } + + private short ToShort(byte[] data) + { + if (isLittleEndian != BitConverter.IsLittleEndian) + Array.Reverse(data); + + return BitConverter.ToInt16(data, 0); + } + + private sbyte ToSByte(byte[] data) + { + // An sbyte should just be a byte with an offset range. + return (sbyte)(data[0] - byte.MaxValue); + } + + /// <summary> + /// Retrieves an array from a byte array using the supplied converter + /// to read each individual element from the supplied byte array + /// </summary> + /// <param name="data"></param> + /// <param name="elementLengthBytes"></param> + /// <param name="converter"></param> + /// <returns></returns> + private Array GetArray<T>(byte[] data, int elementLengthBytes, ConverterMethod<T> converter) + { + Array convertedData = Array.CreateInstance(typeof(T), data.Length / elementLengthBytes); + + var buffer = new byte[elementLengthBytes]; + + // Read each element from the array + for (int elementCount = 0; elementCount < data.Length / elementLengthBytes; elementCount++) + { + // Place the data for the current element into the buffer + Array.Copy(data, elementCount * elementLengthBytes, buffer, 0, elementLengthBytes); + + // Process the data and place it into the output array + convertedData.SetValue(converter(buffer), elementCount); + } + + return convertedData; + } + + /// <summary> + /// A delegate used to invoke any of the data conversion methods + /// </summary> + /// <param name="data"></param> + /// <returns></returns> + private delegate T ConverterMethod<out T>(byte[] data); + + #endregion + + #region Stream seek methods - used to get to locations within the JPEG + + /// <summary> + /// Scans to the Exif block + /// </summary> + private void ReadToExifStart() + { + // The file has a number of blocks (Exif/JFIF), each of which + // has a tag number followed by a length. We scan the document until the required tag (0xFFE1) + // is found. All tags start with FF, so a non FF tag indicates an error. + + // Get the next tag. + byte markerStart; + byte markerNumber = 0; + while (((markerStart = reader.ReadByte()) == 0xFF) && (markerNumber = reader.ReadByte()) != 0xE1) + { + // Get the length of the data. + ushort dataLength = ReadUShort(); + + // Jump to the end of the data (note that the size field includes its own size)! + reader.BaseStream.Seek(dataLength - 2, SeekOrigin.Current); + } + + // It's only success if we found the 0xFFE1 marker + if (markerStart != 0xFF || markerNumber != 0xE1) + throw new Exception("Could not find Exif data block"); + } + + /// <summary> + /// Reads through the Exif data and builds an index of all Exif tags in the document + /// </summary> + /// <returns></returns> + private void CreateTagIndex() + { + // The next 4 bytes are the size of the Exif data. + ReadUShort(); + + // Next is the Exif data itself. It starts with the ASCII "Exif" followed by 2 zero bytes. + if (ReadString(4) != "Exif") + throw new Exception("Exif data not found"); + + // 2 zero bytes + if (ReadUShort() != 0) + throw new Exception("Malformed Exif data"); + + // We're now into the TIFF format + tiffHeaderStart = reader.BaseStream.Position; + + // What byte align will be used for the TIFF part of the document? II for Intel, MM for Motorola + isLittleEndian = ReadString(2) == "II"; + + // Next 2 bytes are always the same. + if (ReadUShort() != 0x002A) + throw new Exception("Error in TIFF data"); + + // Get the offset to the IFD (image file directory) + uint ifdOffset = ReadUint(); + + // Note that this offset is from the first byte of the TIFF header. Jump to the IFD. + fileStream.Position = ifdOffset + tiffHeaderStart; + + // Catalogue this first IFD (there will be another IFD) + CatalogueIFD(); + + // There's more data stored in the subifd, the offset to which is found in tag 0x8769. + // As with all TIFF offsets, it will be relative to the first byte of the TIFF header. + uint offset; + if (!GetTagValue(0x8769, out offset)) + throw new Exception("Unable to locate Exif data"); + + // Jump to the exif SubIFD + fileStream.Position = offset + tiffHeaderStart; + + // Add the subIFD to the catalogue too + CatalogueIFD(); + + // Go to the GPS IFD and catalogue that too. It's an optional + // section. + if (GetTagValue(0x8825, out offset)) + { + // Jump to the GPS SubIFD + fileStream.Position = offset + tiffHeaderStart; + + // Add the subIFD to the catalogue too + CatalogueIFD(); + } + } + + #endregion + + #region Exif data catalog and retrieval methods + + public bool GetTagValue<T>(ExifTags tag, out T result) + { + return GetTagValue((ushort)tag, out result); + } + + /// <summary> + /// Retrieves an Exif value with the requested tag ID + /// </summary> + /// <param name="tagID"></param> + /// <param name="result"></param> + /// <returns></returns> + public bool GetTagValue<T>(ushort tagID, out T result) + { + ushort tiffDataType; + uint numberOfComponents; + byte[] tagData = GetTagBytes(tagID, out tiffDataType, out numberOfComponents); + + if (tagData == null) + { + result = default(T); + return false; + } + + byte fieldLength = GetTIFFFieldLength(tiffDataType); + + // Convert the data to the appropriate datatype. Note the weird boxing via object. + // The compiler doesn't like it otherwise. + switch (tiffDataType) + { + case 1: + // unsigned byte + if (numberOfComponents == 1) + result = (T)(object)tagData[0]; + else + result = (T)(object)tagData; + return true; + case 2: + // ascii string + string str = Encoding.ASCII.GetString(tagData); + + // There may be a null character within the string + int nullCharIndex = str.IndexOf('\0'); + if (nullCharIndex != -1) + str = str.Substring(0, nullCharIndex); + + // Special processing for dates. + if (typeof(T) == typeof(DateTime)) + { + result = + (T)(object)DateTime.ParseExact(str, "yyyy:MM:dd HH:mm:ss", CultureInfo.InvariantCulture); + return true; + } + + result = (T)(object)str; + return true; + case 3: + // unsigned short + if (numberOfComponents == 1) + result = (T)(object)ToUShort(tagData); + else + result = (T)(object)GetArray(tagData, fieldLength, ToUShort); + return true; + case 4: + // unsigned long + if (numberOfComponents == 1) + result = (T)(object)ToUint(tagData); + else + result = (T)(object)GetArray(tagData, fieldLength, ToUint); + return true; + case 5: + // unsigned rational + if (numberOfComponents == 1) + result = (T)(object)ToURational(tagData); + else + result = (T)(object)GetArray(tagData, fieldLength, ToURational); + return true; + case 6: + // signed byte + if (numberOfComponents == 1) + result = (T)(object)ToSByte(tagData); + else + result = (T)(object)GetArray(tagData, fieldLength, ToSByte); + return true; + case 7: + // undefined. Treat it as an unsigned integer. + if (numberOfComponents == 1) + result = (T)(object)ToUint(tagData); + else + result = (T)(object)GetArray(tagData, fieldLength, ToUint); + return true; + case 8: + // Signed short + if (numberOfComponents == 1) + result = (T)(object)ToShort(tagData); + else + result = (T)(object)GetArray(tagData, fieldLength, ToShort); + return true; + case 9: + // Signed long + if (numberOfComponents == 1) + result = (T)(object)ToInt(tagData); + else + result = (T)(object)GetArray(tagData, fieldLength, ToInt); + return true; + case 10: + // signed rational + if (numberOfComponents == 1) + result = (T)(object)ToRational(tagData); + else + result = (T)(object)GetArray(tagData, fieldLength, ToRational); + return true; + case 11: + // single float + if (numberOfComponents == 1) + result = (T)(object)ToSingle(tagData); + else + result = (T)(object)GetArray(tagData, fieldLength, ToSingle); + return true; + case 12: + // double float + if (numberOfComponents == 1) + result = (T)(object)ToDouble(tagData); + else + result = (T)(object)GetArray(tagData, fieldLength, ToDouble); + return true; + default: + throw new Exception(string.Format("Unknown TIFF datatype: {0}", tiffDataType)); + } + } + + /// <summary> + /// Gets the data in the specified tag ID, starting from before the IFD block. + /// </summary> + /// <param name="tiffDataType"></param> + /// <param name="numberOfComponents">The number of items which make up the data item - i.e. for a string, this will be the + /// number of characters in the string</param> + /// <param name="tagID"></param> + private byte[] GetTagBytes(ushort tagID, out ushort tiffDataType, out uint numberOfComponents) + { + // Get the tag's offset from the catalogue and do some basic error checks + if (fileStream == null || reader == null || catalogue == null || !catalogue.ContainsKey(tagID)) + { + tiffDataType = 0; + numberOfComponents = 0; + return null; + } + + long tagOffset = catalogue[tagID]; + + // Jump to the TIFF offset + fileStream.Position = tagOffset; + + // Read the tag number from the file + ushort currentTagID = ReadUShort(); + + if (currentTagID != tagID) + throw new Exception("Tag number not at expected offset"); + + // Read the offset to the Exif IFD + tiffDataType = ReadUShort(); + numberOfComponents = ReadUint(); + byte[] tagData = ReadBytes(4); + + // If the total space taken up by the field is longer than the + // 2 bytes afforded by the tagData, tagData will contain an offset + // to the actual data. + var dataSize = (int)(numberOfComponents * GetTIFFFieldLength(tiffDataType)); + + if (dataSize > 4) + { + ushort offsetAddress = ToUShort(tagData); + return ReadBytes(offsetAddress, dataSize); + } + + // The value is stored in the tagData starting from the left + Array.Resize(ref tagData, dataSize); + + return tagData; + } + + /// <summary> + /// Records all Exif tags and their offsets within + /// the file from the current IFD + /// </summary> + private void CatalogueIFD() + { + if (catalogue == null) + catalogue = new Dictionary<ushort, long>(); + + // Assume we're just before the IFD. + + // First 2 bytes is the number of entries in this IFD + ushort entryCount = ReadUShort(); + + for (ushort currentEntry = 0; currentEntry < entryCount; currentEntry++) + { + ushort currentTagNumber = ReadUShort(); + + // Record this in the catalogue + catalogue[currentTagNumber] = fileStream.Position - 2; + + // Go to the end of this item (10 bytes, as each entry is 12 bytes long) + reader.BaseStream.Seek(10, SeekOrigin.Current); + } + } + + #endregion + + #region IDisposable Members + + public void Dispose() + { + // Make sure the file handle is released + if (reader != null) + reader.Close(); + if (fileStream != null) + fileStream.Close(); + } + + #endregion + } +} diff --git a/MediaBrowser.Providers/Photos/ExifTags.cs b/MediaBrowser.Providers/Photos/ExifTags.cs new file mode 100644 index 000000000..39e153f2e --- /dev/null +++ b/MediaBrowser.Providers/Photos/ExifTags.cs @@ -0,0 +1,132 @@ + +namespace MediaBrowser.Providers.Photos +{ + /// <summary> + /// All exif tags as per the Exif standard 2.2, JEITA CP-2451 + /// </summary> + public enum ExifTags : ushort + { + // IFD0 items + ImageWidth = 0x100, + ImageLength = 0x101, + BitsPerSample = 0x102, + Compression = 0x103, + PhotometricInterpretation = 0x106, + ImageDescription = 0x10E, + Make = 0x10F, + Model = 0x110, + StripOffsets = 0x111, + Orientation = 0x112, + SamplesPerPixel = 0x115, + RowsPerStrip = 0x116, + StripByteCounts = 0x117, + XResolution = 0x11A, + YResolution = 0x11B, + PlanarConfiguration = 0x11C, + ResolutionUnit = 0x128, + TransferFunction = 0x12D, + Software = 0x131, + DateTime = 0x132, + Artist = 0x13B, + WhitePoint = 0x13E, + PrimaryChromaticities = 0x13F, + JPEGInterchangeFormat = 0x201, + JPEGInterchangeFormatLength = 0x202, + YCbCrCoefficients = 0x211, + YCbCrSubSampling = 0x212, + YCbCrPositioning = 0x213, + ReferenceBlackWhite = 0x214, + Copyright = 0x8298, + + // SubIFD items + ExposureTime = 0x829A, + FNumber = 0x829D, + ExposureProgram = 0x8822, + SpectralSensitivity = 0x8824, + ISOSpeedRatings = 0x8827, + OECF = 0x8828, + ExifVersion = 0x9000, + DateTimeOriginal = 0x9003, + DateTimeDigitized = 0x9004, + ComponentsConfiguration = 0x9101, + CompressedBitsPerPixel = 0x9102, + ShutterSpeedValue = 0x9201, + ApertureValue = 0x9202, + BrightnessValue = 0x9203, + ExposureBiasValue = 0x9204, + MaxApertureValue = 0x9205, + SubjectDistance = 0x9206, + MeteringMode = 0x9207, + LightSource = 0x9208, + Flash = 0x9209, + FocalLength = 0x920A, + SubjectArea = 0x9214, + MakerNote = 0x927C, + UserComment = 0x9286, + SubsecTime = 0x9290, + SubsecTimeOriginal = 0x9291, + SubsecTimeDigitized = 0x9292, + FlashpixVersion = 0xA000, + ColorSpace = 0xA001, + PixelXDimension = 0xA002, + PixelYDimension = 0xA003, + RelatedSoundFile = 0xA004, + FlashEnergy = 0xA20B, + SpatialFrequencyResponse = 0xA20C, + FocalPlaneXResolution = 0xA20E, + FocalPlaneYResolution = 0xA20F, + FocalPlaneResolutionUnit = 0xA210, + SubjectLocation = 0xA214, + ExposureIndex = 0xA215, + SensingMethod = 0xA217, + FileSource = 0xA300, + SceneType = 0xA301, + CFAPattern = 0xA302, + CustomRendered = 0xA401, + ExposureMode = 0xA402, + WhiteBalance = 0xA403, + DigitalZoomRatio = 0xA404, + FocalLengthIn35mmFilm = 0xA405, + SceneCaptureType = 0xA406, + GainControl = 0xA407, + Contrast = 0xA408, + Saturation = 0xA409, + Sharpness = 0xA40A, + DeviceSettingDescription = 0xA40B, + SubjectDistanceRange = 0xA40C, + ImageUniqueID = 0xA420, + + // GPS subifd items + GPSVersionID = 0x0, + GPSLatitudeRef = 0x1, + GPSLatitude = 0x2, + GPSLongitudeRef = 0x3, + GPSLongitude = 0x4, + GPSAltitudeRef = 0x5, + GPSAltitude = 0x6, + GPSTimeStamp = 0x7, + GPSSatellites = 0x8, + GPSStatus = 0x9, + GPSMeasureMode = 0xA, + GPSDOP = 0xB, + GPSSpeedRef = 0xC, + GPSSpeed = 0xD, + GPSTrackRef = 0xE, + GPSTrack = 0xF, + GPSImgDirectionRef = 0x10, + GPSImgDirection = 0x11, + GPSMapDatum = 0x12, + GPSDestLatitudeRef = 0x13, + GPSDestLatitude = 0x14, + GPSDestLongitudeRef = 0x15, + GPSDestLongitude = 0x16, + GPSDestBearingRef = 0x17, + GPSDestBearing = 0x18, + GPSDestDistanceRef = 0x19, + GPSDestDistance = 0x1A, + GPSProcessingMethod = 0x1B, + GPSAreaInformation = 0x1C, + GPSDateStamp = 0x1D, + GPSDifferential = 0x1E + } +} diff --git a/MediaBrowser.Providers/Photos/PhotoHelper.cs b/MediaBrowser.Providers/Photos/PhotoHelper.cs new file mode 100644 index 000000000..a5ce6f81f --- /dev/null +++ b/MediaBrowser.Providers/Photos/PhotoHelper.cs @@ -0,0 +1,113 @@ +using MediaBrowser.Controller.Entities; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MediaBrowser.Providers.Photos +{ + public static class PhotoHelper + { + public static List<BaseItem> ShuffleList(List<BaseItem> list) + { + var rnd = new Random(DateTime.Now.Second); + for (var i = 1; i < list.Count; i++) + { + var pos = rnd.Next(i + 1); + var x = list[i]; + list[i] = list[pos]; + list[pos] = x; + } + return list; + } + + public static string Dec2Frac(double dbl) + { + char neg = ' '; + double dblDecimal = dbl; + if (dblDecimal == (int)dblDecimal) return dblDecimal.ToString(); //return no if it's not a decimal + if (dblDecimal < 0) + { + dblDecimal = Math.Abs(dblDecimal); + neg = '-'; + } + var whole = (int)Math.Truncate(dblDecimal); + string decpart = dblDecimal.ToString().Replace(Math.Truncate(dblDecimal) + ".", ""); + double rN = Convert.ToDouble(decpart); + double rD = Math.Pow(10, decpart.Length); + + string rd = Recur(decpart); + int rel = Convert.ToInt32(rd); + if (rel != 0) + { + rN = rel; + rD = (int)Math.Pow(10, rd.Length) - 1; + } + //just a few prime factors for testing purposes + var primes = new[] { 47, 43, 37, 31, 29, 23, 19, 17, 13, 11, 7, 5, 3, 2 }; + foreach (int i in primes) ReduceNo(i, ref rD, ref rN); + + rN = rN + (whole * rD); + return string.Format("{0}{1}/{2}", neg, rN, rD); + } + + /// <summary> + /// Finds out the recurring decimal in a specified number + /// </summary> + /// <param name="db">Number to check</param> + /// <returns></returns> + private static string Recur(string db) + { + if (db.Length < 13) return "0"; + var sb = new StringBuilder(); + for (int i = 0; i < 7; i++) + { + sb.Append(db[i]); + int dlength = (db.Length / sb.ToString().Length); + int occur = Occurence(sb.ToString(), db); + if (dlength == occur || dlength == occur - sb.ToString().Length) + { + return sb.ToString(); + } + } + return "0"; + } + + /// <summary> + /// Checks for number of occurence of specified no in a number + /// </summary> + /// <param name="s">The no to check occurence times</param> + /// <param name="check">The number where to check this</param> + /// <returns></returns> + private static int Occurence(string s, string check) + { + int i = 0; + int d = s.Length; + string ds = check; + for (int n = (ds.Length / d); n > 0; n--) + { + if (ds.Contains(s)) + { + i++; + ds = ds.Remove(ds.IndexOf(s, System.StringComparison.Ordinal), d); + } + } + return i; + } + + /// <summary> + /// Reduces a fraction given the numerator and denominator + /// </summary> + /// <param name="i">Number to use in an attempt to reduce fraction</param> + /// <param name="rD">the Denominator</param> + /// <param name="rN">the Numerator</param> + private static void ReduceNo(int i, ref double rD, ref double rN) + { + //keep reducing until divisibility ends + while ((rD % i) < 1e-10 && (rN % i) < 1e-10) + { + rN = rN / i; + rD = rD / i; + } + } + } +} diff --git a/MediaBrowser.Providers/Photos/PhotoMetadataService.cs b/MediaBrowser.Providers/Photos/PhotoMetadataService.cs new file mode 100644 index 000000000..2a7895e16 --- /dev/null +++ b/MediaBrowser.Providers/Photos/PhotoMetadataService.cs @@ -0,0 +1,32 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Providers.Manager; +using System.Collections.Generic; + +namespace MediaBrowser.Providers.Photos +{ + class PhotoMetadataService : MetadataService<Photo, ItemLookupInfo> + { + public PhotoMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem) + : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem) + { + } + + /// <summary> + /// Merges the specified source. + /// </summary> + /// <param name="source">The source.</param> + /// <param name="target">The target.</param> + /// <param name="lockedFields">The locked fields.</param> + /// <param name="replaceData">if set to <c>true</c> [replace data].</param> + /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param> + protected override void MergeData(Photo source, Photo target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + { + ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); + } + } +} diff --git a/MediaBrowser.Providers/Photos/PhotoProvider.cs b/MediaBrowser.Providers/Photos/PhotoProvider.cs new file mode 100644 index 000000000..23ad5230d --- /dev/null +++ b/MediaBrowser.Providers/Photos/PhotoProvider.cs @@ -0,0 +1,137 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using System; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.Photos +{ + public class PhotoProvider : ICustomMetadataProvider<Photo>, IHasChangeMonitor + { + private readonly ILogger _logger; + + public PhotoProvider(ILogger logger) + { + _logger = logger; + } + + public Task<ItemUpdateType> FetchAsync(Photo item, IDirectoryService directoryService, CancellationToken cancellationToken) + { + item.SetImagePath(ImageType.Primary, item.Path); + + if (item.Path.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) || item.Path.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase)) + { + try + { + using (var reader = new ExifReader(item.Path)) + { + double aperture = 0; + double shutterSpeed = 0; + + DateTime dateTaken; + + string manufacturer; + string model; + + int xResolution; + int yResolution; + + reader.GetTagValue(ExifTags.FNumber, out aperture); + reader.GetTagValue(ExifTags.ExposureTime, out shutterSpeed); + reader.GetTagValue(ExifTags.DateTimeOriginal, out dateTaken); + + reader.GetTagValue(ExifTags.Make, out manufacturer); + reader.GetTagValue(ExifTags.Model, out model); + + reader.GetTagValue(ExifTags.XResolution, out xResolution); + reader.GetTagValue(ExifTags.YResolution, out yResolution); + + if (dateTaken > DateTime.MinValue) + { + item.DateCreated = dateTaken; + item.PremiereDate = dateTaken; + item.ProductionYear = dateTaken.Year; + } + + var cameraModel = manufacturer ?? string.Empty; + cameraModel += " "; + cameraModel += model ?? string.Empty; + + item.Overview = "Taken " + dateTaken.ToString("F") + "\n" + + (!string.IsNullOrWhiteSpace(cameraModel) ? "With a " + cameraModel : "") + + (aperture > 0 && shutterSpeed > 0 ? " at f" + aperture.ToString(CultureInfo.InvariantCulture) + " and " + PhotoHelper.Dec2Frac(shutterSpeed) + "s" : "") + "\n" + + (xResolution > 0 ? "\n<br/>Resolution: " + xResolution + "x" + yResolution : ""); + } + + } + catch (Exception e) + { + _logger.ErrorException("Image Provider - Error reading image tag for {0}", e, item.Path); + } + } + + //// Get additional tags from xmp + //try + //{ + // using (var fs = new FileStream(item.Path, FileMode.Open, FileAccess.Read)) + // { + // var bf = BitmapFrame.Create(fs); + + // if (bf != null) + // { + // var data = (BitmapMetadata)bf.Metadata; + // if (data != null) + // { + + // DateTime dateTaken; + // var cameraModel = ""; + + // DateTime.TryParse(data.DateTaken, out dateTaken); + // if (dateTaken > DateTime.MinValue) item.DateCreated = dateTaken; + // cameraModel = data.CameraModel; + + // item.PremiereDate = dateTaken; + // item.ProductionYear = dateTaken.Year; + // item.Overview = "Taken " + dateTaken.ToString("F") + "\n" + + // (cameraModel != "" ? "With a " + cameraModel : "") + + // (aperture > 0 && shutterSpeed > 0 ? " at f" + aperture.ToString(CultureInfo.InvariantCulture) + " and " + PhotoHelper.Dec2Frac(shutterSpeed) + "s" : "") + "\n" + // + (bf.Width > 0 ? "\n<br/>Resolution: " + (int)bf.Width + "x" + (int)bf.Height : ""); + + // var photo = item as Photo; + // if (data.Keywords != null) item.Genres = photo.Tags = new List<string>(data.Keywords); + // item.Name = !string.IsNullOrWhiteSpace(data.Title) ? data.Title : item.Name; + // item.CommunityRating = data.Rating; + // if (!string.IsNullOrWhiteSpace(data.Subject)) photo.AddTagline(data.Subject); + // } + // } + + // } + //} + //catch (NotSupportedException) + //{ + // // No problem - move on + //} + //catch (Exception e) + //{ + // _logger.ErrorException("Error trying to read extended data from {0}", e, item.Path); + //} + + const ItemUpdateType result = ItemUpdateType.ImageUpdate | ItemUpdateType.MetadataImport; + return Task.FromResult(result); + } + + public string Name + { + get { return "Embedded Information"; } + } + + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) + { + return item.DateModified > date; + } + } +} diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs index 006ab827d..994b70902 100644 --- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs +++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs @@ -54,12 +54,7 @@ namespace MediaBrowser.Providers.Studios }; } - public Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) - { - return GetImages(item, imageType == ImageType.Primary, imageType == ImageType.Thumb, cancellationToken); - } - - public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + public Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken) { return GetImages(item, true, true, cancellationToken); } diff --git a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs index 6f8da573d..908094bdd 100644 --- a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs +++ b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs @@ -58,14 +58,7 @@ namespace MediaBrowser.Providers.TV }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) - { - var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); - - return images.Where(i => i.Type == imageType); - } - - public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken) { var list = new List<RemoteImageInfo>(); diff --git a/MediaBrowser.Providers/TV/FanartSeriesProvider.cs b/MediaBrowser.Providers/TV/FanartSeriesProvider.cs index f18189a2a..1b9f62dd4 100644 --- a/MediaBrowser.Providers/TV/FanartSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/FanartSeriesProvider.cs @@ -69,14 +69,7 @@ namespace MediaBrowser.Providers.TV }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) - { - var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); - - return images.Where(i => i.Type == imageType); - } - - public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken) { var list = new List<RemoteImageInfo>(); diff --git a/MediaBrowser.Providers/TV/MovieDbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/MovieDbSeriesImageProvider.cs index 9795b7b11..3516fcbd1 100644 --- a/MediaBrowser.Providers/TV/MovieDbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/TV/MovieDbSeriesImageProvider.cs @@ -51,14 +51,7 @@ namespace MediaBrowser.Providers.TV }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) - { - var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); - - return images.Where(i => i.Type == imageType); - } - - public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken) { var list = new List<RemoteImageInfo>(); diff --git a/MediaBrowser.Providers/TV/MovieDbSeriesProvider.cs b/MediaBrowser.Providers/TV/MovieDbSeriesProvider.cs index c169fba79..5dd8a056f 100644 --- a/MediaBrowser.Providers/TV/MovieDbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/MovieDbSeriesProvider.cs @@ -50,22 +50,37 @@ namespace MediaBrowser.Providers.TV var result = new MetadataResult<Series>(); var tmdbId = info.GetProviderId(MetadataProviders.Tmdb); - var imdbId = info.GetProviderId(MetadataProviders.Imdb); - var tvdbId = info.GetProviderId(MetadataProviders.Tvdb); - // Commenting our searching by imdb/tvdb because as of now it's not supported. - // But this is how movies work so most likely this can eventually be enabled. + if (string.IsNullOrEmpty(tmdbId)) + { + var imdbId = info.GetProviderId(MetadataProviders.Imdb); - if (string.IsNullOrEmpty(tmdbId) /*&& string.IsNullOrEmpty(imdbId) && string.IsNullOrEmpty(tvdbId)*/) + if (!string.IsNullOrEmpty(imdbId)) + { + tmdbId = await FindIdByExternalId(imdbId, "imdb_id", cancellationToken).ConfigureAwait(false); + } + } + + if (string.IsNullOrEmpty(tmdbId)) + { + var tvdbId = info.GetProviderId(MetadataProviders.Tvdb); + + if (!string.IsNullOrEmpty(tvdbId)) + { + tmdbId = await FindIdByExternalId(tvdbId, "tvdb_id", cancellationToken).ConfigureAwait(false); + } + } + + if (string.IsNullOrEmpty(tmdbId)) { tmdbId = await new MovieDbSearch(_logger, _jsonSerializer).FindSeriesId(info, cancellationToken).ConfigureAwait(false); } - if (!string.IsNullOrEmpty(tmdbId) /*|| !string.IsNullOrEmpty(imdbId) || !string.IsNullOrEmpty(tvdbId)*/) + if (!string.IsNullOrEmpty(tmdbId)) { cancellationToken.ThrowIfCancellationRequested(); - result.Item = await FetchMovieData(tmdbId, imdbId, tvdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); + result.Item = await FetchMovieData(tmdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); result.HasMetadata = result.Item != null; } @@ -73,43 +88,29 @@ namespace MediaBrowser.Providers.TV return result; } - private async Task<Series> FetchMovieData(string tmdbId, string imdbId, string tvdbId, string language, string preferredCountryCode, CancellationToken cancellationToken) + private async Task<Series> FetchMovieData(string tmdbId, string language, string preferredCountryCode, CancellationToken cancellationToken) { string dataFilePath = null; RootObject seriesInfo = null; - // Id could be ImdbId or TmdbId - if (string.IsNullOrEmpty(tmdbId)) + if (!string.IsNullOrEmpty(tmdbId)) { - if (string.IsNullOrWhiteSpace(imdbId)) - { - seriesInfo = await FetchMainResult(imdbId, language, cancellationToken).ConfigureAwait(false); - } - if (seriesInfo == null) - { - if (string.IsNullOrWhiteSpace(imdbId)) - { - seriesInfo = await FetchMainResult(tvdbId, language, cancellationToken).ConfigureAwait(false); - } - } + seriesInfo = await FetchMainResult(tmdbId, language, cancellationToken).ConfigureAwait(false); + } - if (seriesInfo == null) - { - return null; - } + if (seriesInfo == null) + { + return null; + } - tmdbId = seriesInfo.id.ToString(_usCulture); + tmdbId = seriesInfo.id.ToString(_usCulture); - dataFilePath = MovieDbProvider.Current.GetDataFilePath(tmdbId, language); - Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath)); - _jsonSerializer.SerializeToFile(seriesInfo, dataFilePath); - } + dataFilePath = MovieDbProvider.Current.GetDataFilePath(tmdbId, language); + Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath)); + _jsonSerializer.SerializeToFile(seriesInfo, dataFilePath); await EnsureSeriesInfo(tmdbId, language, cancellationToken).ConfigureAwait(false); - dataFilePath = dataFilePath ?? GetDataFilePath(tmdbId, language); - seriesInfo = seriesInfo ?? _jsonSerializer.DeserializeFromFile<RootObject>(dataFilePath); - var item = new Series(); ProcessMainInfo(item, preferredCountryCode, seriesInfo); @@ -223,8 +224,6 @@ namespace MediaBrowser.Providers.TV url += string.Format("&language={0}", language); } - RootObject mainResult; - cancellationToken.ThrowIfCancellationRequested(); using (var json = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions @@ -235,38 +234,8 @@ namespace MediaBrowser.Providers.TV }).ConfigureAwait(false)) { - mainResult = _jsonSerializer.DeserializeFromStream<RootObject>(json); - } - - cancellationToken.ThrowIfCancellationRequested(); - - if (mainResult != null && string.IsNullOrEmpty(mainResult.overview)) - { - if (!string.IsNullOrEmpty(language) && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase)) - { - _logger.Info("Couldn't find meta for language " + language + ". Trying English..."); - - url = string.Format(GetTvInfo3, id, MovieDbProvider.ApiKey) + "&include_image_language=en,null&language=en"; - - using (var json = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions - { - Url = url, - CancellationToken = cancellationToken, - AcceptHeader = MovieDbProvider.AcceptHeader - - }).ConfigureAwait(false)) - { - mainResult = _jsonSerializer.DeserializeFromStream<RootObject>(json); - } - - if (String.IsNullOrEmpty(mainResult.overview)) - { - _logger.Error("Unable to find information for (id:" + id + ")"); - return null; - } - } + return _jsonSerializer.DeserializeFromStream<RootObject>(json); } - return mainResult; } private readonly Task _cachedTask = Task.FromResult(true); @@ -338,6 +307,37 @@ namespace MediaBrowser.Providers.TV return false; } + private async Task<string> FindIdByExternalId(string id, string externalSource, CancellationToken cancellationToken) + { + var url = string.Format("http://api.themoviedb.org/3/tv/find/{0}?api_key={1}&external_source={2}", + id, + MovieDbProvider.ApiKey, + externalSource); + + using (var json = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions + { + Url = url, + CancellationToken = cancellationToken, + AcceptHeader = MovieDbProvider.AcceptHeader + + }).ConfigureAwait(false)) + { + var result = _jsonSerializer.DeserializeFromStream<MovieDbSearch.ExternalIdLookupResult>(json); + + if (result != null && result.tv_results != null) + { + var tv = result.tv_results.FirstOrDefault(); + + if (tv != null) + { + return tv.id.ToString(_usCulture); + } + } + } + + return null; + } + public class CreatedBy { public int id { get; set; } diff --git a/MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs index 0830b6713..36e349f60 100644 --- a/MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs @@ -51,14 +51,7 @@ namespace MediaBrowser.Providers.TV }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) - { - var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); - - return images.Where(i => i.Type == imageType); - } - - public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + public Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken) { var episode = (Episode)item; var series = episode.Series; diff --git a/MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs index e4756ea71..c0c103b7f 100644 --- a/MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs @@ -59,14 +59,7 @@ namespace MediaBrowser.Providers.TV }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) - { - var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); - - return images.Where(i => i.Type == imageType); - } - - public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken) { var season = (Season)item; var series = season.Series; diff --git a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs index 765d17aa7..761c77443 100644 --- a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs @@ -59,14 +59,7 @@ namespace MediaBrowser.Providers.TV }; } - public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) - { - var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); - - return images.Where(i => i.Type == imageType); - } - - public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken) { var series = (Series)item; var seriesId = series.GetProviderId(MetadataProviders.Tvdb); diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 735565e25..b42541204 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -462,21 +462,27 @@ namespace MediaBrowser.Server.Implementations.Library return item; } + public BaseItem ResolvePath(FileSystemInfo fileInfo, Folder parent = null) + { + return ResolvePath(fileInfo, new DirectoryService(_logger), parent); + } + /// <summary> /// Resolves a path into a BaseItem /// </summary> /// <param name="fileInfo">The file info.</param> + /// <param name="directoryService">The directory service.</param> /// <param name="parent">The parent.</param> /// <returns>BaseItem.</returns> - /// <exception cref="System.ArgumentNullException"></exception> - public BaseItem ResolvePath(FileSystemInfo fileInfo, Folder parent = null) + /// <exception cref="System.ArgumentNullException">fileInfo</exception> + public BaseItem ResolvePath(FileSystemInfo fileInfo, IDirectoryService directoryService, Folder parent = null) { if (fileInfo == null) { throw new ArgumentNullException("fileInfo"); } - var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, this) + var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, this, directoryService) { Parent = parent, Path = fileInfo.FullName, @@ -497,8 +503,6 @@ namespace MediaBrowser.Server.Implementations.Library // When resolving the root, we need it's grandchildren (children of user views) var flattenFolderDepth = isPhysicalRoot ? 2 : 0; - var directoryService = new DirectoryService(_logger); - var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, _fileSystem, _logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf); // Need to remove subpaths that may have been resolved from shortcuts @@ -555,9 +559,10 @@ namespace MediaBrowser.Server.Implementations.Library /// </summary> /// <typeparam name="T"></typeparam> /// <param name="files">The files.</param> + /// <param name="directoryService">The directory service.</param> /// <param name="parent">The parent.</param> /// <returns>List{``0}.</returns> - public List<T> ResolvePaths<T>(IEnumerable<FileSystemInfo> files, Folder parent) + public List<T> ResolvePaths<T>(IEnumerable<FileSystemInfo> files, IDirectoryService directoryService, Folder parent) where T : BaseItem { var list = new List<T>(); @@ -566,7 +571,7 @@ namespace MediaBrowser.Server.Implementations.Library { try { - var item = ResolvePath(f, parent) as T; + var item = ResolvePath(f, directoryService, parent) as T; if (item != null) { @@ -594,10 +599,7 @@ namespace MediaBrowser.Server.Implementations.Library { var rootFolderPath = ConfigurationManager.ApplicationPaths.RootFolderPath; - if (!Directory.Exists(rootFolderPath)) - { - Directory.CreateDirectory(rootFolderPath); - } + Directory.CreateDirectory(rootFolderPath); var rootFolder = RetrieveItem(rootFolderPath.GetMBId(typeof(AggregateFolder))) as AggregateFolder ?? (AggregateFolder)ResolvePath(new DirectoryInfo(rootFolderPath)); diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index f6fd11960..7e643cd99 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; using System; @@ -62,14 +63,17 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio /// Determine if the supplied file data points to a music album /// </summary> /// <param name="path">The path.</param> + /// <param name="directoryService">The directory service.</param> /// <returns><c>true</c> if [is music album] [the specified data]; otherwise, <c>false</c>.</returns> - public static bool IsMusicAlbum(string path) + public static bool IsMusicAlbum(string path, IDirectoryService directoryService) { // If list contains at least 2 audio files or at least one and no video files consider it to contain music var foundAudio = 0; - foreach (var fullName in Directory.EnumerateFiles(path)) + foreach (var file in directoryService.GetFiles(path)) { + var fullName = file.FullName; + if (EntityResolutionHelper.IsAudioFile(fullName)) { // Don't resolve these into audio files @@ -105,7 +109,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio if (ContainsMusic(args.FileSystemChildren)) return true; } - return false; } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index 54a32c367..4fa97fc9d 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -57,9 +57,11 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio { return null; } + + var directoryService = args.DirectoryService; // If we contain an album assume we are an artist folder - return args.FileSystemChildren.Where(i => (i.Attributes & FileAttributes.Directory) == FileAttributes.Directory).Any(i => MusicAlbumResolver.IsMusicAlbum(i.FullName)) ? new MusicArtist() : null; + return args.FileSystemChildren.Where(i => (i.Attributes & FileAttributes.Directory) == FileAttributes.Directory).Any(i => MusicAlbumResolver.IsMusicAlbum(i.FullName, directoryService)) ? new MusicArtist() : null; } } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index f355f4bf6..2d4540713 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; using System; @@ -92,31 +93,31 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies if (args.Path.IndexOf("[trailers]", StringComparison.OrdinalIgnoreCase) != -1 || string.Equals(collectionType, CollectionType.Trailers, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<Trailer>(args.Path, args.Parent, args.FileSystemChildren); + return FindMovie<Trailer>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService); } if (args.Path.IndexOf("[musicvideos]", StringComparison.OrdinalIgnoreCase) != -1 || string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren); + return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService); } if (args.Path.IndexOf("[adultvideos]", StringComparison.OrdinalIgnoreCase) != -1 || string.Equals(collectionType, CollectionType.AdultVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<AdultVideo>(args.Path, args.Parent, args.FileSystemChildren); + return FindMovie<AdultVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService); } if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren); + return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService); } if (string.IsNullOrEmpty(collectionType) || string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase) || string.Equals(collectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<Movie>(args.Path, args.Parent, args.FileSystemChildren); + return FindMovie<Movie>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService); } return null; @@ -203,7 +204,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies /// <param name="parent">The parent.</param> /// <param name="fileSystemEntries">The file system entries.</param> /// <returns>Movie.</returns> - private T FindMovie<T>(string path, Folder parent, IEnumerable<FileSystemInfo> fileSystemEntries) + private T FindMovie<T>(string path, Folder parent, IEnumerable<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService) where T : Video, new() { var movies = new List<T>(); @@ -248,7 +249,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies continue; } - var childArgs = new ItemResolveArgs(_applicationPaths, _libraryManager) + var childArgs = new ItemResolveArgs(_applicationPaths, _libraryManager, directoryService) { FileInfo = child, Path = child.FullName, diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs new file mode 100644 index 000000000..35261dc3b --- /dev/null +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -0,0 +1,50 @@ +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using System; +using System.Linq; + +namespace MediaBrowser.Server.Implementations.Library.Resolvers +{ + public class PhotoResolver : ItemResolver<Photo> + { + private readonly IServerApplicationPaths _applicationPaths; + + /// <summary> + /// Initializes a new instance of the <see cref="PhotoResolver" /> class. + /// </summary> + /// <param name="applicationPaths">The application paths.</param> + public PhotoResolver(IServerApplicationPaths applicationPaths) + { + _applicationPaths = applicationPaths; + } + + /// <summary> + /// Resolves the specified args. + /// </summary> + /// <param name="args">The args.</param> + /// <returns>Trailer.</returns> + protected override Photo Resolve(ItemResolveArgs args) + { + // Must be an image file within a photo collection + if (!args.IsDirectory && IsImageFile(args.Path) && string.Equals(args.GetCollectionType(), "photos", StringComparison.OrdinalIgnoreCase)) + { + return new Photo + { + Path = args.Path + }; + } + + return null; + } + + protected static string[] ImageExtensions = { ".tiff", ".jpg", ".png", ".aiff" }; + protected bool IsImageFile(string path) + { + return !path.EndsWith("folder.jpg", StringComparison.OrdinalIgnoreCase) + && !path.EndsWith("backdrop.jpg", StringComparison.OrdinalIgnoreCase) + && ImageExtensions.Any(p => path.EndsWith(p, StringComparison.OrdinalIgnoreCase)); + } + + } +} diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 8489624fc..050af69d0 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -140,6 +140,7 @@ <Compile Include="IO\LibraryMonitor.cs" /> <Compile Include="Library\CoreResolutionIgnoreRule.cs" /> <Compile Include="Library\LibraryManager.cs" /> + <Compile Include="Library\Resolvers\PhotoResolver.cs" /> <Compile Include="Library\SearchEngine.cs" /> <Compile Include="Library\ResolverHelper.cs" /> <Compile Include="Library\Resolvers\Audio\AudioResolver.cs" /> diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 695697f8f..5af08073b 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -31,7 +31,6 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.System; using MediaBrowser.Model.Updates; -using MediaBrowser.Providers; using MediaBrowser.Providers.Manager; using MediaBrowser.Server.Implementations; using MediaBrowser.Server.Implementations.BdInfo; @@ -242,6 +241,52 @@ namespace MediaBrowser.ServerApplication LogManager.RemoveConsoleOutput(); } + public override Task Init(IProgress<double> progress) + { + DeleteDeprecatedModules(); + + return base.Init(progress); + } + + private void DeleteDeprecatedModules() + { + try + { + File.Delete(Path.Combine(ApplicationPaths.PluginsPath, "MBPhoto.dll")); + } + catch (IOException) + { + // Not there, no big deal + } + + try + { + Directory.Delete(Path.Combine(ApplicationPaths.DataPath, "remote-images"), true); + } + catch (IOException) + { + // Not there, no big deal + } + + try + { + Directory.Delete(Path.Combine(ApplicationPaths.DataPath, "extracted-video-images"), true); + } + catch (IOException) + { + // Not there, no big deal + } + + try + { + Directory.Delete(Path.Combine(ApplicationPaths.DataPath, "extracted-audio-images"), true); + } + catch (IOException) + { + // Not there, no big deal + } + } + /// <summary> /// Registers resources that classes will depend on /// </summary> diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 4d9fdaf08..8bc2ea226 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Common.Internal</id> - <version>3.0.326</version> + <version>3.0.327</version> <title>MediaBrowser.Common.Internal</title> <authors>Luke</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.326" /> + <dependency id="MediaBrowser.Common" version="3.0.327" /> <dependency id="NLog" version="2.1.0" /> <dependency id="SimpleInjector" version="2.4.1" /> <dependency id="sharpcompress" version="0.10.2" /> diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index e90771374..ced8ed35a 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Common</id> - <version>3.0.326</version> + <version>3.0.327</version> <title>MediaBrowser.Common</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 2650eabb9..5d435b248 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata> <id>MediaBrowser.Server.Core</id> - <version>3.0.326</version> + <version>3.0.327</version> <title>Media Browser.Server.Core</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains core components required to build plugins for Media Browser Server.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.326" /> + <dependency id="MediaBrowser.Common" version="3.0.327" /> </dependencies> </metadata> <files> |
