diff --git a/MediaBrowser.Controller/Entities/Photo.cs b/MediaBrowser.Controller/Entities/Photo.cs index 96995c315b..542fbaa31c 100644 --- a/MediaBrowser.Controller/Entities/Photo.cs +++ b/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; } } } diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 99c4a46296..aaa44b009f 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -287,6 +287,9 @@ Drawing\DrawingUtils.cs + + Drawing\ImageOrientation.cs + Drawing\ImageOutputFormat.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 919f676f19..6046d36068 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -250,6 +250,9 @@ Drawing\DrawingUtils.cs + + Drawing\ImageOrientation.cs + Drawing\ImageOutputFormat.cs diff --git a/MediaBrowser.Model/Drawing/ImageOrientation.cs b/MediaBrowser.Model/Drawing/ImageOrientation.cs new file mode 100644 index 0000000000..3fdfaf7bf1 --- /dev/null +++ b/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, + } +} diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index bb93fa6878..c3f02236fb 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -81,6 +81,7 @@ + diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 757c7a7534..b69a4bc2f5 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -56,6 +56,9 @@ ..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll + + ..\packages\taglib.2.1.0.0\lib\policy.2.0.taglib-sharp.dll + @@ -64,6 +67,10 @@ + + ..\packages\taglib.2.1.0.0\lib\taglib-sharp.dll + True + @@ -142,8 +149,6 @@ - - diff --git a/MediaBrowser.Providers/Photos/ExifReader.cs b/MediaBrowser.Providers/Photos/ExifReader.cs deleted file mode 100644 index 8526a7f2a3..0000000000 --- a/MediaBrowser.Providers/Photos/ExifReader.cs +++ /dev/null @@ -1,613 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Text; - -namespace MediaBrowser.Providers.Photos -{ - /// - /// A class for reading Exif data from a JPEG file. The file will be open for reading for as long as the class exists. - /// - /// - public class ExifReader : IDisposable - { - private readonly FileStream fileStream = null; - private readonly BinaryReader reader = null; - - /// - /// The catalogue of tag ids and their absolute offsets within the - /// file - /// - private Dictionary catalogue; - - /// - /// Indicates whether to read data using big or little endian byte aligns - /// - private bool isLittleEndian; - - /// - /// The position in the filestream at which the TIFF header starts - /// - 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 - - /// - /// Returns the length (in bytes) per component of the specified TIFF data type - /// - /// - 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 - - /// - /// Gets a 2 byte unsigned integer from the file - /// - /// - private ushort ReadUShort() - { - return ToUShort(ReadBytes(2)); - } - - /// - /// Gets a 4 byte unsigned integer from the file - /// - /// - 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); - } - - /// - /// Reads some bytes from the specified TIFF offset - /// - /// - /// - /// - 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 - - /// - /// Converts 2 bytes to a ushort using the current byte aligns - /// - /// - private ushort ToUShort(byte[] data) - { - if (isLittleEndian != BitConverter.IsLittleEndian) - Array.Reverse(data); - - return BitConverter.ToUInt16(data, 0); - } - - /// - /// Converts 8 bytes to an unsigned rational using the current byte aligns. - /// - /// - /// - /// - 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; - } - - /// - /// Converts 8 bytes to a signed rational using the current byte aligns. - /// - /// - /// A TIFF rational contains 2 4-byte integers, the first of which is - /// the numerator, and the second of which is the denominator. - /// - /// - /// - 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; - } - - /// - /// Converts 4 bytes to a uint using the current byte aligns - /// - /// - private uint ToUint(byte[] data) - { - if (isLittleEndian != BitConverter.IsLittleEndian) - Array.Reverse(data); - - return BitConverter.ToUInt32(data, 0); - } - - /// - /// Converts 4 bytes to an int using the current byte aligns - /// - /// - 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); - } - - /// - /// Retrieves an array from a byte array using the supplied converter - /// to read each individual element from the supplied byte array - /// - /// - /// - /// - /// - private Array GetArray(byte[] data, int elementLengthBytes, ConverterMethod 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; - } - - /// - /// A delegate used to invoke any of the data conversion methods - /// - /// - /// - private delegate T ConverterMethod(byte[] data); - - #endregion - - #region Stream seek methods - used to get to locations within the JPEG - - /// - /// Scans to the Exif block - /// - 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"); - } - - /// - /// Reads through the Exif data and builds an index of all Exif tags in the document - /// - /// - 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(ExifTags tag, out T result) - { - return GetTagValue((ushort)tag, out result); - } - - /// - /// Retrieves an Exif value with the requested tag ID - /// - /// - /// - /// - public bool GetTagValue(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)); - } - } - - /// - /// Gets the data in the specified tag ID, starting from before the IFD block. - /// - /// - /// 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 - /// - 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; - } - - /// - /// Records all Exif tags and their offsets within - /// the file from the current IFD - /// - private void CatalogueIFD() - { - if (catalogue == null) - catalogue = new Dictionary(); - - // 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 deleted file mode 100644 index 39e153f2eb..0000000000 --- a/MediaBrowser.Providers/Photos/ExifTags.cs +++ /dev/null @@ -1,132 +0,0 @@ - -namespace MediaBrowser.Providers.Photos -{ - /// - /// All exif tags as per the Exif standard 2.2, JEITA CP-2451 - /// - 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 index a5ce6f81f8..2334c792e7 100644 --- a/MediaBrowser.Providers/Photos/PhotoHelper.cs +++ b/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 ShuffleList(List 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 = ' '; diff --git a/MediaBrowser.Providers/Photos/PhotoProvider.cs b/MediaBrowser.Providers/Photos/PhotoProvider.cs index c0f53e0c37..01d36b5416 100644 --- a/MediaBrowser.Providers/Photos/PhotoProvider.cs +++ b/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 + var file = File.Create(item.Path); + + var image = file as TagLib.Image.File; + + var tag = file.GetTag(TagTypes.TiffIFD) as IFDTag; + + if (tag != null) { - using (var reader = new ExifReader(item.Path)) + var structure = tag.Structure; + + if (structure != null) { - double aperture = 0; - double shutterSpeed = 0; + var exif = structure.GetEntry(0, (ushort)IFDEntryTag.ExifIFD) as SubIFDEntry; - DateTime dateTaken; - - string manufacturer; - string model; - - 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); - - 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; - - var size = _imageProcessor.GetImageSize(item.Path); - var xResolution = size.Width; - var yResolution = size.Height; - - 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
Resolution: " + xResolution + "x" + yResolution : ""); } - } - catch (Exception e) + + item.CameraManufacturer = image.ImageTag.Make; + item.CameraModel = image.ImageTag.Model; + + var rating = image.ImageTag.Rating; + if (rating.HasValue) { - _logger.ErrorException("Image Provider - Error reading image tag for {0}", e, item.Path); + item.CommunityRating = rating; } + else + { + item.CommunityRating = null; + } + + item.Overview = image.ImageTag.Comment; + + if (!string.IsNullOrWhiteSpace(image.ImageTag.Title)) + { + item.Name = image.ImageTag.Title; + } + + var dateTaken = image.ImageTag.DateTime; + if (dateTaken.HasValue) + { + item.DateCreated = dateTaken.Value; + item.PremiereDate = dateTaken.Value; + item.ProductionYear = dateTaken.Value.Year; + } + + 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); } - - //// 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
Resolution: " + (int)bf.Width + "x" + (int)bf.Height : ""); - - // var photo = item as Photo; - // if (data.Keywords != null) item.Genres = photo.Tags = new List(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); diff --git a/MediaBrowser.Providers/packages.config b/MediaBrowser.Providers/packages.config index 9d3b60ff5f..29a8021566 100644 --- a/MediaBrowser.Providers/packages.config +++ b/MediaBrowser.Providers/packages.config @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 42191a2701..2d050d4a75 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/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(), null, TimeSpan.FromHours(6), TimeSpan.FromHours(6)); + _timer = new Timer(s => _createdRules = new List(), 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 diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index 6b1f3d91ac..0a9209bfe2 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/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:" } diff --git a/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloadInfo.cs b/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloadInfo.cs index 97a3ee190b..7a74e9689f 100644 --- a/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloadInfo.cs +++ b/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" };