take photos into the core

pull/702/head
Luke Pulverenti 10 years ago
parent 9254c37d52
commit eec9e04825

@ -1159,6 +1159,72 @@ namespace MediaBrowser.Api.Playback
return null;
}
/// <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>
@ -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.");

@ -60,16 +60,12 @@ namespace MediaBrowser.Api.Playback
[ApiMember(Name = "Static", Description = "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
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

@ -189,7 +189,7 @@ namespace MediaBrowser.Api
{
throw new ArgumentNullException("xmlSerializer");
}
_xmlSerializer = xmlSerializer;
_userManager = userManager;
_dtoService = dtoService;

@ -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;
}
}
}

@ -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,

@ -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;
}
}
}

@ -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,

@ -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>

@ -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();
}
}

@ -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;
}
}
}

@ -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;
}
}
}
}

@ -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;
}
}
}

@ -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;
}
}
}

@ -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;
}
}
}

@ -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;
}
}
}

@ -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;

@ -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>

@ -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>

@ -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" />

@ -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.

@ -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);

@ -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);

@ -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);
}

@ -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);
}

@ -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;
}
}
}

@ -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>();
}
}

@ -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);

@ -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" />

@ -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>();

@ -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>();

@ -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; }
}
}
}

@ -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);

@ -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);

@ -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;

@ -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;

@ -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>();

@ -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);
}

@ -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
{

@ -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);

@ -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

@ -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
}
}

@ -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
}
}

@ -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;
}
}
}
}

@ -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);
}
}
}

@ -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;
}
}
}

@ -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);
}

@ -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>();

@ -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>();

@ -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>();

@ -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; }

@ -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;

@ -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;

@ -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);

@ -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));

@ -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;
}

@ -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;
}
}

@ -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,

@ -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));
}
}
}

@ -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" />

@ -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>

@ -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" />

@ -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>

@ -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>

Loading…
Cancel
Save