Selaa lähdekoodia

Fixes #901 - Improve captured photo metadata

Luke Pulverenti 11 vuotta sitten
vanhempi
sitoutus
93ded925a7

+ 15 - 1
MediaBrowser.Controller/Entities/Photo.cs

@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using MediaBrowser.Model.Drawing;
+using System.Collections.Generic;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -20,5 +21,18 @@ namespace MediaBrowser.Controller.Entities
                 return Model.Entities.MediaType.Photo;
             }
         }
+
+        public int? Width { get; set; }
+        public int? Height { get; set; }
+        public string CameraManufacturer { get; set; }
+        public string CameraModel { get; set; }
+        public string Software { get; set; }
+        public double? ExposureTime { get; set; }
+        public double? FocalLength { get; set; }
+
+        public ImageOrientation? Orientation { get; set; }
+
+        public double? Aperture { get; set; }
+        public double? ShutterSpeed { get; set; }
     }
 }

+ 3 - 0
MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj

@@ -287,6 +287,9 @@
     <Compile Include="..\MediaBrowser.Model\Drawing\DrawingUtils.cs">
       <Link>Drawing\DrawingUtils.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Drawing\ImageOrientation.cs">
+      <Link>Drawing\ImageOrientation.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Drawing\ImageOutputFormat.cs">
       <Link>Drawing\ImageOutputFormat.cs</Link>
     </Compile>

+ 3 - 0
MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj

@@ -250,6 +250,9 @@
     <Compile Include="..\MediaBrowser.Model\Drawing\DrawingUtils.cs">
       <Link>Drawing\DrawingUtils.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Drawing\ImageOrientation.cs">
+      <Link>Drawing\ImageOrientation.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Drawing\ImageOutputFormat.cs">
       <Link>Drawing\ImageOutputFormat.cs</Link>
     </Compile>

+ 16 - 0
MediaBrowser.Model/Drawing/ImageOrientation.cs

@@ -0,0 +1,16 @@
+
+namespace MediaBrowser.Model.Drawing
+{
+    public enum ImageOrientation
+    {
+        None = 0,
+        TopLeft = 1,
+        TopRight = 2,
+        BottomRight = 3,
+        BottomLeft = 4,
+        LeftTop = 5,
+        RightTop = 6,
+        RightBottom = 7,
+        LeftBottom = 8,
+    }
+}

+ 1 - 0
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -81,6 +81,7 @@
     <Compile Include="Configuration\ChapterOptions.cs" />
     <Compile Include="Configuration\XbmcMetadataOptions.cs" />
     <Compile Include="Configuration\SubtitlePlaybackMode.cs" />
+    <Compile Include="Drawing\ImageOrientation.cs" />
     <Compile Include="FileOrganization\AutoOrganizeOptions.cs" />
     <Compile Include="FileOrganization\TvFileOrganizationOptions.cs" />
     <Compile Include="Configuration\BaseApplicationConfiguration.cs" />

+ 7 - 2
MediaBrowser.Providers/MediaBrowser.Providers.csproj

@@ -56,6 +56,9 @@
     <Reference Include="MoreLinq">
       <HintPath>..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll</HintPath>
     </Reference>
+    <Reference Include="policy.2.0.taglib-sharp">
+      <HintPath>..\packages\taglib.2.1.0.0\lib\policy.2.0.taglib-sharp.dll</HintPath>
+    </Reference>
     <Reference Include="System" />
     <Reference Include="System.Core" />
     <Reference Include="System.Net" />
@@ -64,6 +67,10 @@
     <Reference Include="Microsoft.CSharp" />
     <Reference Include="System.Data" />
     <Reference Include="System.Xml" />
+    <Reference Include="taglib-sharp">
+      <HintPath>..\packages\taglib.2.1.0.0\lib\taglib-sharp.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
   </ItemGroup>
   <ItemGroup>
     <Compile Include="..\SharedVersion.cs">
@@ -142,8 +149,6 @@
     <Compile Include="Music\MusicBrainzAlbumProvider.cs" />
     <Compile Include="People\PersonMetadataService.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" />

+ 0 - 613
MediaBrowser.Providers/Photos/ExifReader.cs

@@ -1,613 +0,0 @@
-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 - 132
MediaBrowser.Providers/Photos/ExifTags.cs

@@ -1,132 +0,0 @@
-
-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
-    }
-}

+ 1 - 16
MediaBrowser.Providers/Photos/PhotoHelper.cs

@@ -1,25 +1,10 @@
-using MediaBrowser.Controller.Entities;
-using System;
-using System.Collections.Generic;
+using System;
 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 = ' ';

+ 84 - 79
MediaBrowser.Providers/Photos/PhotoProvider.cs

@@ -5,9 +5,13 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using System;
-using System.Globalization;
+using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using TagLib;
+using TagLib.IFD;
+using TagLib.IFD.Entries;
+using TagLib.IFD.Tags;
 
 namespace MediaBrowser.Providers.Photos
 {
@@ -27,100 +31,101 @@ namespace MediaBrowser.Providers.Photos
             item.SetImagePath(ImageType.Primary, item.Path);
             item.SetImagePath(ImageType.Backdrop, item.Path);
 
-            if (item.Path.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) || item.Path.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase))
+            // Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs
+
+            try
             {
-                try
-                {
-                    using (var reader = new ExifReader(item.Path))
-                    {
-                        double aperture = 0;
-                        double shutterSpeed = 0;
+                var file = File.Create(item.Path);
 
-                        DateTime dateTaken;
+                var image = file as TagLib.Image.File;
 
-                        string manufacturer;
-                        string model;
+                var tag = file.GetTag(TagTypes.TiffIFD) as IFDTag;
 
-                        reader.GetTagValue(ExifTags.FNumber, out aperture);
-                        reader.GetTagValue(ExifTags.ExposureTime, out shutterSpeed);
-                        reader.GetTagValue(ExifTags.DateTimeOriginal, out dateTaken);
+                if (tag != null)
+                {
+                    var structure = tag.Structure;
 
-                        reader.GetTagValue(ExifTags.Make, out manufacturer);
-                        reader.GetTagValue(ExifTags.Model, out model);
+                    if (structure != null)
+                    {
+                        var exif = structure.GetEntry(0, (ushort)IFDEntryTag.ExifIFD) as SubIFDEntry;
 
-                        if (dateTaken > DateTime.MinValue)
+                        if (exif != null)
                         {
-                            item.DateCreated = dateTaken;
-                            item.PremiereDate = dateTaken;
-                            item.ProductionYear = dateTaken.Year;
+                            var exifStructure = exif.Structure;
+
+                            if (exifStructure != null)
+                            {
+                                var entry = exifStructure.GetEntry(0, (ushort)ExifEntryTag.ApertureValue) as RationalIFDEntry;
+
+                                if (entry != null)
+                                {
+                                    double val = entry.Value.Numerator;
+                                    val /= entry.Value.Denominator;
+                                    item.Aperture = val;
+                                }
+
+                                entry = exifStructure.GetEntry(0, (ushort)ExifEntryTag.ShutterSpeedValue) as RationalIFDEntry;
+
+                                if (entry != null)
+                                {
+                                    double val = entry.Value.Numerator;
+                                    val /= entry.Value.Denominator;
+                                    item.ShutterSpeed = val;
+                                }
+                            }
                         }
+                    }
+                }
 
-                        var cameraModel = manufacturer ?? string.Empty;
-                        cameraModel += " ";
-                        cameraModel += model ?? string.Empty;
+                item.CameraManufacturer = image.ImageTag.Make;
+                item.CameraModel = image.ImageTag.Model;
 
-                        var size = _imageProcessor.GetImageSize(item.Path);
-                        var xResolution = size.Width;
-                        var yResolution = size.Height;
+                var rating = image.ImageTag.Rating;
+                if (rating.HasValue)
+                {
+                    item.CommunityRating = rating;
+                }
+                else
+                {
+                    item.CommunityRating = null;
+                }
 
-                        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 : "");
-                    }
+                item.Overview = image.ImageTag.Comment;
 
+                if (!string.IsNullOrWhiteSpace(image.ImageTag.Title))
+                {
+                    item.Name = image.ImageTag.Title;
                 }
-                catch (Exception e)
+
+                var dateTaken = image.ImageTag.DateTime;
+                if (dateTaken.HasValue)
                 {
-                    _logger.ErrorException("Image Provider - Error reading image tag for {0}", e, item.Path);
+                    item.DateCreated = dateTaken.Value;
+                    item.PremiereDate = dateTaken.Value;
+                    item.ProductionYear = dateTaken.Value.Year;
                 }
-            }
 
-            //// 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);
-            //}
+                var size = _imageProcessor.GetImageSize(item.Path);
+                item.Height = Convert.ToInt32(size.Height);
+                item.Width = Convert.ToInt32(size.Width);
+
+                item.Genres = image.ImageTag.Genres.ToList();
+                item.Tags = image.ImageTag.Keywords.ToList();
+                item.Software = image.ImageTag.Software;
+
+                Model.Drawing.ImageOrientation orientation;
+                if (Enum.TryParse(image.ImageTag.Orientation.ToString(), true, out orientation))
+                {
+                    item.Orientation = orientation;
+                }
+
+                item.ExposureTime = image.ImageTag.ExposureTime;
+                item.FocalLength = image.ImageTag.FocalLength;
+            }
+            catch (Exception e)
+            {
+                _logger.ErrorException("Image Provider - Error reading image tag for {0}", e, item.Path);
+            }
 
             const ItemUpdateType result = ItemUpdateType.ImageUpdate | ItemUpdateType.MetadataImport;
             return Task.FromResult(result);

+ 1 - 0
MediaBrowser.Providers/packages.config

@@ -2,4 +2,5 @@
 <packages>
   <package id="MediaBrowser.BdInfo" version="1.0.0.10" targetFramework="net45" />
   <package id="morelinq" version="1.0.16006" targetFramework="net45" />
+  <package id="taglib" version="2.1.0.0" targetFramework="net45" />
 </packages>

+ 9 - 9
MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs

@@ -61,7 +61,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
 
                 // Mono.Nat does never rise this event. The event is there however it is useless. 
                 // You could remove it with no risk. 
-                // NatUtility.DeviceLost += NatUtility_DeviceLost;
+                NatUtility.DeviceLost += NatUtility_DeviceLost;
 
 
                 // it is hard to say what one should do when an unhandled exception is raised
@@ -71,7 +71,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
 
                 _isStarted = true;
 
-                _timer = new Timer(s => _createdRules = new List<string>(), null, TimeSpan.FromHours(6), TimeSpan.FromHours(6));
+                _timer = new Timer(s => _createdRules = new List<string>(), null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
             }
         }
 
@@ -123,7 +123,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
             if (!_createdRules.Contains(address))
             {
                 _createdRules.Add(address);
-                
+
                 var info = _appHost.GetSystemInfo();
 
                 CreatePortMap(device, info.HttpServerPortNumber);
@@ -141,11 +141,11 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
         }
 
         // As I said before, this method will be never invoked. You can remove it.
-        //void NatUtility_DeviceLost(object sender, DeviceEventArgs e)
-        //{
-        //    var device = e.Device;
-        //    _logger.Debug("NAT device lost: {0}", device.LocalAddress.ToString());
-        //}
+        void NatUtility_DeviceLost(object sender, DeviceEventArgs e)
+        {
+            var device = e.Device;
+            _logger.Debug("NAT device lost: {0}", device.LocalAddress.ToString());
+        }
 
         public void Dispose()
         {
@@ -167,7 +167,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
                 // This is not a significant improvement
                 NatUtility.StopDiscovery();
                 NatUtility.DeviceFound -= NatUtility_DeviceFound;
-                //NatUtility.DeviceLost -= NatUtility_DeviceLost;
+                NatUtility.DeviceLost -= NatUtility_DeviceLost;
                 NatUtility.UnhandledException -= NatUtility_UnhandledException;
             }
             // Statements in try-block will no fail because StopDiscovery is a one-line 

+ 3 - 1
MediaBrowser.Server.Implementations/Localization/Server/server.json

@@ -1105,5 +1105,7 @@
     "OptionDirector": "Director",
     "OptionGuestStar": "Guest star",
     "OptionProducer": "Producer",
-    "OptionWriter": "Writer"
+    "OptionWriter": "Writer",
+    "LabelAirDays": "Air days:",
+    "LabelAirTime":  "Air time:"
 }

+ 11 - 2
MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloadInfo.cs

@@ -35,7 +35,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg
                     switch (arg)
                     {
                         case "Version":
-                            return "20140612";
+                            return "20140827";
                         case "FFMpegFilename":
                             return "ffmpeg.exe";
                         case "FFProbeFilename":
@@ -111,9 +111,18 @@ namespace MediaBrowser.ServerApplication.FFMpeg
             switch (pid)
             {
                 case PlatformID.Win32NT:
+                    if (PlatformDetection.IsX86_64)
+                    {
+                        return new[]
+                        {
+                            "http://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-20140827-git-9e8ab36-win64-static.7z",
+                            "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/windows/ffmpeg-20140612-git-3a1c895-win32-static.7z"
+                        };
+                    }
+
                     return new[]
                     {
-                        "http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20140612-git-3a1c895-win32-static.7z",
+                        "http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20140827-git-9e8ab36-win32-static.7z",
                         "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/windows/ffmpeg-20140612-git-3a1c895-win32-static.7z"
                     };