Fixes #901 - Improve captured photo metadata
This commit is contained in:
parent
809e4629c0
commit
93ded925a7
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using MediaBrowser.Model.Drawing;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Entities
|
namespace MediaBrowser.Controller.Entities
|
||||||
{
|
{
|
||||||
|
@ -20,5 +21,18 @@ namespace MediaBrowser.Controller.Entities
|
||||||
return Model.Entities.MediaType.Photo;
|
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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -287,6 +287,9 @@
|
||||||
<Compile Include="..\MediaBrowser.Model\Drawing\DrawingUtils.cs">
|
<Compile Include="..\MediaBrowser.Model\Drawing\DrawingUtils.cs">
|
||||||
<Link>Drawing\DrawingUtils.cs</Link>
|
<Link>Drawing\DrawingUtils.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\MediaBrowser.Model\Drawing\ImageOrientation.cs">
|
||||||
|
<Link>Drawing\ImageOrientation.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="..\MediaBrowser.Model\Drawing\ImageOutputFormat.cs">
|
<Compile Include="..\MediaBrowser.Model\Drawing\ImageOutputFormat.cs">
|
||||||
<Link>Drawing\ImageOutputFormat.cs</Link>
|
<Link>Drawing\ImageOutputFormat.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|
|
@ -250,6 +250,9 @@
|
||||||
<Compile Include="..\MediaBrowser.Model\Drawing\DrawingUtils.cs">
|
<Compile Include="..\MediaBrowser.Model\Drawing\DrawingUtils.cs">
|
||||||
<Link>Drawing\DrawingUtils.cs</Link>
|
<Link>Drawing\DrawingUtils.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\MediaBrowser.Model\Drawing\ImageOrientation.cs">
|
||||||
|
<Link>Drawing\ImageOrientation.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="..\MediaBrowser.Model\Drawing\ImageOutputFormat.cs">
|
<Compile Include="..\MediaBrowser.Model\Drawing\ImageOutputFormat.cs">
|
||||||
<Link>Drawing\ImageOutputFormat.cs</Link>
|
<Link>Drawing\ImageOutputFormat.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|
16
MediaBrowser.Model/Drawing/ImageOrientation.cs
Normal file
16
MediaBrowser.Model/Drawing/ImageOrientation.cs
Normal file
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -81,6 +81,7 @@
|
||||||
<Compile Include="Configuration\ChapterOptions.cs" />
|
<Compile Include="Configuration\ChapterOptions.cs" />
|
||||||
<Compile Include="Configuration\XbmcMetadataOptions.cs" />
|
<Compile Include="Configuration\XbmcMetadataOptions.cs" />
|
||||||
<Compile Include="Configuration\SubtitlePlaybackMode.cs" />
|
<Compile Include="Configuration\SubtitlePlaybackMode.cs" />
|
||||||
|
<Compile Include="Drawing\ImageOrientation.cs" />
|
||||||
<Compile Include="FileOrganization\AutoOrganizeOptions.cs" />
|
<Compile Include="FileOrganization\AutoOrganizeOptions.cs" />
|
||||||
<Compile Include="FileOrganization\TvFileOrganizationOptions.cs" />
|
<Compile Include="FileOrganization\TvFileOrganizationOptions.cs" />
|
||||||
<Compile Include="Configuration\BaseApplicationConfiguration.cs" />
|
<Compile Include="Configuration\BaseApplicationConfiguration.cs" />
|
||||||
|
|
|
@ -56,6 +56,9 @@
|
||||||
<Reference Include="MoreLinq">
|
<Reference Include="MoreLinq">
|
||||||
<HintPath>..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll</HintPath>
|
<HintPath>..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll</HintPath>
|
||||||
</Reference>
|
</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" />
|
||||||
<Reference Include="System.Core" />
|
<Reference Include="System.Core" />
|
||||||
<Reference Include="System.Net" />
|
<Reference Include="System.Net" />
|
||||||
|
@ -64,6 +67,10 @@
|
||||||
<Reference Include="Microsoft.CSharp" />
|
<Reference Include="Microsoft.CSharp" />
|
||||||
<Reference Include="System.Data" />
|
<Reference Include="System.Data" />
|
||||||
<Reference Include="System.Xml" />
|
<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>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\SharedVersion.cs">
|
<Compile Include="..\SharedVersion.cs">
|
||||||
|
@ -142,8 +149,6 @@
|
||||||
<Compile Include="Music\MusicBrainzAlbumProvider.cs" />
|
<Compile Include="Music\MusicBrainzAlbumProvider.cs" />
|
||||||
<Compile Include="People\PersonMetadataService.cs" />
|
<Compile Include="People\PersonMetadataService.cs" />
|
||||||
<Compile Include="People\MovieDbPersonProvider.cs" />
|
<Compile Include="People\MovieDbPersonProvider.cs" />
|
||||||
<Compile Include="Photos\ExifReader.cs" />
|
|
||||||
<Compile Include="Photos\ExifTags.cs" />
|
|
||||||
<Compile Include="Photos\PhotoHelper.cs" />
|
<Compile Include="Photos\PhotoHelper.cs" />
|
||||||
<Compile Include="Photos\PhotoMetadataService.cs" />
|
<Compile Include="Photos\PhotoMetadataService.cs" />
|
||||||
<Compile Include="Photos\PhotoProvider.cs" />
|
<Compile Include="Photos\PhotoProvider.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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,25 +1,10 @@
|
||||||
using MediaBrowser.Controller.Entities;
|
using System;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Photos
|
namespace MediaBrowser.Providers.Photos
|
||||||
{
|
{
|
||||||
public static class PhotoHelper
|
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)
|
public static string Dec2Frac(double dbl)
|
||||||
{
|
{
|
||||||
char neg = ' ';
|
char neg = ' ';
|
||||||
|
|
|
@ -5,9 +5,13 @@ using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using TagLib;
|
||||||
|
using TagLib.IFD;
|
||||||
|
using TagLib.IFD.Entries;
|
||||||
|
using TagLib.IFD.Tags;
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Photos
|
namespace MediaBrowser.Providers.Photos
|
||||||
{
|
{
|
||||||
|
@ -27,100 +31,101 @@ namespace MediaBrowser.Providers.Photos
|
||||||
item.SetImagePath(ImageType.Primary, item.Path);
|
item.SetImagePath(ImageType.Primary, item.Path);
|
||||||
item.SetImagePath(ImageType.Backdrop, 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;
|
var exif = structure.GetEntry(0, (ushort)IFDEntryTag.ExifIFD) as SubIFDEntry;
|
||||||
double shutterSpeed = 0;
|
|
||||||
|
|
||||||
DateTime dateTaken;
|
if (exif != null)
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
item.DateCreated = dateTaken;
|
var exifStructure = exif.Structure;
|
||||||
item.PremiereDate = dateTaken;
|
|
||||||
item.ProductionYear = dateTaken.Year;
|
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<br/>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<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;
|
const ItemUpdateType result = ItemUpdateType.ImageUpdate | ItemUpdateType.MetadataImport;
|
||||||
return Task.FromResult(result);
|
return Task.FromResult(result);
|
||||||
|
|
|
@ -2,4 +2,5 @@
|
||||||
<packages>
|
<packages>
|
||||||
<package id="MediaBrowser.BdInfo" version="1.0.0.10" targetFramework="net45" />
|
<package id="MediaBrowser.BdInfo" version="1.0.0.10" targetFramework="net45" />
|
||||||
<package id="morelinq" version="1.0.16006" targetFramework="net45" />
|
<package id="morelinq" version="1.0.16006" targetFramework="net45" />
|
||||||
|
<package id="taglib" version="2.1.0.0" targetFramework="net45" />
|
||||||
</packages>
|
</packages>
|
|
@ -61,7 +61,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
||||||
|
|
||||||
// Mono.Nat does never rise this event. The event is there however it is useless.
|
// Mono.Nat does never rise this event. The event is there however it is useless.
|
||||||
// You could remove it with no risk.
|
// 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
|
// 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;
|
_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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,11 +141,11 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
||||||
}
|
}
|
||||||
|
|
||||||
// As I said before, this method will be never invoked. You can remove it.
|
// As I said before, this method will be never invoked. You can remove it.
|
||||||
//void NatUtility_DeviceLost(object sender, DeviceEventArgs e)
|
void NatUtility_DeviceLost(object sender, DeviceEventArgs e)
|
||||||
//{
|
{
|
||||||
// var device = e.Device;
|
var device = e.Device;
|
||||||
// _logger.Debug("NAT device lost: {0}", device.LocalAddress.ToString());
|
_logger.Debug("NAT device lost: {0}", device.LocalAddress.ToString());
|
||||||
//}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
@ -167,7 +167,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
||||||
// This is not a significant improvement
|
// This is not a significant improvement
|
||||||
NatUtility.StopDiscovery();
|
NatUtility.StopDiscovery();
|
||||||
NatUtility.DeviceFound -= NatUtility_DeviceFound;
|
NatUtility.DeviceFound -= NatUtility_DeviceFound;
|
||||||
//NatUtility.DeviceLost -= NatUtility_DeviceLost;
|
NatUtility.DeviceLost -= NatUtility_DeviceLost;
|
||||||
NatUtility.UnhandledException -= NatUtility_UnhandledException;
|
NatUtility.UnhandledException -= NatUtility_UnhandledException;
|
||||||
}
|
}
|
||||||
// Statements in try-block will no fail because StopDiscovery is a one-line
|
// Statements in try-block will no fail because StopDiscovery is a one-line
|
||||||
|
|
|
@ -1105,5 +1105,7 @@
|
||||||
"OptionDirector": "Director",
|
"OptionDirector": "Director",
|
||||||
"OptionGuestStar": "Guest star",
|
"OptionGuestStar": "Guest star",
|
||||||
"OptionProducer": "Producer",
|
"OptionProducer": "Producer",
|
||||||
"OptionWriter": "Writer"
|
"OptionWriter": "Writer",
|
||||||
|
"LabelAirDays": "Air days:",
|
||||||
|
"LabelAirTime": "Air time:"
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
||||||
switch (arg)
|
switch (arg)
|
||||||
{
|
{
|
||||||
case "Version":
|
case "Version":
|
||||||
return "20140612";
|
return "20140827";
|
||||||
case "FFMpegFilename":
|
case "FFMpegFilename":
|
||||||
return "ffmpeg.exe";
|
return "ffmpeg.exe";
|
||||||
case "FFProbeFilename":
|
case "FFProbeFilename":
|
||||||
|
@ -111,9 +111,18 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
||||||
switch (pid)
|
switch (pid)
|
||||||
{
|
{
|
||||||
case PlatformID.Win32NT:
|
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[]
|
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"
|
"https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/windows/ffmpeg-20140612-git-3a1c895-win32-static.7z"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user