diff options
| author | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-02-13 00:11:54 -0500 |
|---|---|---|
| committer | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-02-13 00:11:54 -0500 |
| commit | eec9e0482525c400e9dc7cb17bc000434adba105 (patch) | |
| tree | 73f51bc882804ff92b82d1e85a46a6cec10b6d51 /MediaBrowser.Providers | |
| parent | 9254c37d52af3d16ec9e46b3e211ecc7dc4f1617 (diff) | |
take photos into the core
Diffstat (limited to 'MediaBrowser.Providers')
32 files changed, 1164 insertions, 240 deletions
diff --git a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs index b7672a6a71..e82c6a1b91 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 85aa9f7166..12f14676fd 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 007eeab7bc..4d19bb91e9 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 beece997df..8b085a05ac 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 44abeac436..43d4f20c16 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 6e994c9f28..025945461e 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 5cf4b25919..85f988344a 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 26395e9ccb..b115694954 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 8a38e294c6..9828a20bcb 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 5a2b865f54..5f5ece4a8f 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 a8f3b8a6d9..3de85593a5 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 79238fd514..10a197b377 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 8479b0c6e9..93db4f5b49 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 9b32ff4f27..d96178059e 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 701e74da00..b55a973516 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 0867156e39..6c239ab30a 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 db5f1b8cea..2e85e54011 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 3f8278bdae..2ac9fdfa0a 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 769f077889..63d0546647 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 0000000000..8526a7f2a3 --- /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 0000000000..39e153f2eb --- /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 0000000000..a5ce6f81f8 --- /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 0000000000..2a7895e16f --- /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 0000000000..23ad5230df --- /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 006ab827d2..994b70902d 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 6f8da573db..908094bdd5 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 f18189a2ac..1b9f62dd4a 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 9795b7b110..3516fcbd11 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 c169fba793..5dd8a056f5 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 0830b67137..36e349f600 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 e4756ea719..c0c103b7fb 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 765d17aa75..761c774435 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); |
