/Meta.Core/ImageMeta/ImageProcessor.cs

https://bitbucket.org/muruge/meta-public · C# · 645 lines · 423 code · 125 blank · 97 comment · 52 complexity · 4dcc9110e16bf2b0663edc3a07033d2d MD5 · raw file

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Text.RegularExpressions;
  8. using System.Windows.Media.Imaging;
  9. using Meta.Core.Converters;
  10. namespace Meta.Core.ImageMeta
  11. {
  12. public class ImageProcessor : Processor<IImageProperties>, IImageProcessor
  13. {
  14. #region constructors
  15. public ImageProcessor(string path)
  16. :base(path)
  17. {
  18. }
  19. #endregion
  20. #region public methods
  21. #region static methods
  22. public static BitmapImage Load(string path)
  23. {
  24. var image = new BitmapImage();
  25. image.BeginInit();
  26. image.CacheOption = BitmapCacheOption.OnLoad; //cache the image into memory and close the actual image file.
  27. image.UriSource = new Uri(path);
  28. image.EndInit();
  29. return image;
  30. }
  31. /// <summary>
  32. /// Checks if the file is an image file.
  33. /// </summary>
  34. /// <param name="path">Full Path of the file</param>
  35. /// <returns>Boolean value indicating wheather or not the file is an image file.</returns>
  36. public static bool IsImageFile(string path)
  37. {
  38. var fileExtension = System.IO.Path.GetExtension(path);
  39. var imageFileExtensions = new[] { ".jpg", ".tif", ".tiff", ".jpeg", ".png", "gif", ".bmp", ".jfif", ".jpe" };
  40. bool isImageFile = false;
  41. if (imageFileExtensions
  42. .Any(imageFileExtension => imageFileExtension.Equals(fileExtension, StringComparison.CurrentCultureIgnoreCase)))
  43. isImageFile = true;
  44. return isImageFile;
  45. }
  46. /// <summary>
  47. /// Checks if the format of the image file is currently supported.
  48. /// </summary>
  49. /// <returns>Boolean value indicating wheather or not image format is supported.</returns>
  50. public static bool IsSupportedFormat(string path)
  51. {
  52. return new ImageProcessor(path)
  53. .IsSupportedFormat();
  54. }
  55. #endregion
  56. #region instance methods
  57. /// <summary>
  58. /// Writes the metadata properties into the image file.
  59. /// </summary>
  60. /// <param name="properties">Metadata properties to be written.</param>
  61. /// <returns>Boolean value indicating whether or not write succeeded.</returns>
  62. public override bool TryWrite(IImageProperties properties)
  63. {
  64. try
  65. {
  66. if (!IsPathValid())
  67. return IsWriteSuccessfull;
  68. //FileAccess.ReadWrite access required to overwrite the file with modified metadata
  69. using (Stream originalFileStream = File.Open(Path, FileMode.Open, FileAccess.ReadWrite))
  70. {
  71. //create a temporary file to save the image with new metadata
  72. string directory = System.IO.Path.GetDirectoryName(Path);
  73. string tempFile = "";
  74. //byte array to store the contents of image file
  75. var buffer = new byte[originalFileStream.Length];
  76. //byte array reads the contents of the stream
  77. originalFileStream.Read(buffer, 0, (int)originalFileStream.Length);
  78. //create a memory stream to work with the image.
  79. using (var memoryStream = new MemoryStream(buffer))
  80. {
  81. //use memmory stream to create the BitmapDecoder. This way we work only with the stream in
  82. //memory and release the lock in the original file, so it can be used by other processes.
  83. //BitmapCreateOptions.PreservePixelFormat | BitmapCreateOptions.IgnoreColorProfile
  84. //is required to preserve the pixel format of the image
  85. //BitmapCacheOption.None says to wait until the decoding is complete. It doesnot cache the image
  86. //and accesses it for every request.
  87. BitmapDecoder decoder = BitmapDecoder.Create(memoryStream,
  88. BitmapCreateOptions.PreservePixelFormat | BitmapCreateOptions.IgnoreColorProfile,
  89. BitmapCacheOption.None);
  90. #region Metadata processing
  91. if (decoder.Frames[0] != null && decoder.Frames[0].Metadata != null)
  92. {
  93. //metadata will be frozen. So clone it to allow modification
  94. var metaData = decoder.Frames[0].Metadata.Clone() as BitmapMetadata;
  95. // additional space added to image frame so it can embrace the new metadata.
  96. const uint paddingAmount = 5000;
  97. //padding can only exist only in IFD, EXIF and XMP formats
  98. if (metaData != null)
  99. {
  100. metaData.SetQuery("/app1/ifd/PaddingSchema:Padding", paddingAmount);
  101. metaData.SetQuery("/app1/ifd/exif/PaddingSchema:Padding", paddingAmount);
  102. metaData.SetQuery("/xmp/PaddingSchema:Padding", paddingAmount);
  103. }
  104. string imgFormat = string.Empty;
  105. BitmapEncoder encoder = new JpegBitmapEncoder();
  106. if (decoder.CodecInfo != null)
  107. if (decoder.CodecInfo.FileExtensions.Contains("jpg"))
  108. {
  109. imgFormat = "JPEG";
  110. encoder = new JpegBitmapEncoder();
  111. tempFile = directory + @"\temp.jpg";
  112. }
  113. else if (decoder.CodecInfo.FileExtensions.Contains("tiff"))
  114. {
  115. imgFormat = "TIFF";
  116. encoder = new TiffBitmapEncoder();
  117. tempFile = directory + @"\temp.tiff";
  118. }
  119. var iptc = new IptcStandard(imgFormat);
  120. var xmp = new XmpStandard(imgFormat);
  121. var exif = new ExifStandard(imgFormat);
  122. var ifd = new IfdStandard(imgFormat);
  123. //assign new metadata value
  124. #region Values Assignment
  125. //Title
  126. ProcessProperty(ref metaData, properties.Title, "System.Title", new[] { iptc.ObjectName, ifd.Title });
  127. //Author
  128. if (string.IsNullOrEmpty(properties.Authors))
  129. DeletePathIfExist();
  130. else
  131. {
  132. IList<string> authorsList = Utility.GetListItems(properties.Authors);
  133. if (authorsList.Any())
  134. {
  135. CreatePathIfNotExist(ref metaData, new[] { iptc.ByLine, ifd.Authors });
  136. var auth = new ReadOnlyCollection<string>(authorsList);
  137. metaData.Author = auth;
  138. //removed because it causes some encoding error.
  139. //metaData.SetQuery(exif.Artist, ASCIIEncoding.ASCII.GetBytes(this.Authors));
  140. }
  141. }
  142. //Subject
  143. ProcessProperty(ref metaData, properties.Subject, "System.Subject", new[] { ifd.Subject });
  144. //Explicit mapping to EXIF and IPTC equivalents.
  145. ProcessProperty(ref metaData, properties.Subject, new[] { xmp.Description, iptc.Headline, exif.ImageDescription });
  146. //Keywords
  147. if (string.IsNullOrEmpty(properties.Keywords))
  148. DeletePathIfExist();
  149. else
  150. {
  151. IList<string> keywordsList = Utility.GetListItems(properties.Keywords);
  152. if (keywordsList.Any())
  153. {
  154. CreatePathIfNotExist(ref metaData, new[] { iptc.Keywords });
  155. metaData.Keywords = new ReadOnlyCollection<string>(keywordsList);
  156. //Explicit write required because System.Keywords deletes IFD keyword path.
  157. //Unicode encoding required because datatype of values is unicode byte[].
  158. string keywords = keywordsList
  159. .Aggregate(string.Empty, (current, keyword) => current + (string.IsNullOrEmpty(current) ? keyword : Utility.Delimiter + keyword));
  160. metaData.SetQuery(ifd.Keywords, Encoding.Unicode.GetBytes(keywords.ToCharArray()));
  161. }
  162. }
  163. //Comments
  164. ProcessProperty(ref metaData, properties.Comments, "System.Comment", new[] { iptc.Caption, exif.UserComment, ifd.Comments });
  165. //Date
  166. DateTime date;
  167. if (DateTime.TryParse(properties.Date, out date))//to assign only if it is a valid date
  168. {
  169. metaData.DateTaken = date.ToShortDateString();
  170. // setting date using handler does write to exif though it is suppose to.
  171. //hence explicit writing is done for EXIF and IPTC
  172. metaData.SetQuery(exif.DateTimeOriginal, date.ToString("yyyy:MM:dd HH:mm:ss"));
  173. metaData.SetQuery(iptc.DateCreated, date.ToString("yyyyMMdd"));
  174. }
  175. else //removes all date paths if date does not has a valid value.
  176. {
  177. DeletePathIfExist();
  178. }
  179. //Park Code
  180. ProcessProperty(ref metaData, properties.City, new[] { iptc.City, xmp.City });
  181. //Access Constraint
  182. if (string.IsNullOrEmpty(properties.Urgency.ToString()))
  183. DeletePathIfExist();
  184. else
  185. {
  186. metaData.SetQuery(iptc.Urgency, (int)properties.Urgency);
  187. metaData.SetQuery(xmp.Urgency, ((int)properties.Urgency).ToString());
  188. }
  189. //Copyright Notice
  190. ProcessProperty(ref metaData, properties.CopyrightNotice, "System.Copyright", new[] { iptc.Copyright, ifd.Copyright });
  191. //Location Details
  192. ProcessProperty(ref metaData, properties.Location, new[] { iptc.SpecialInstructions, xmp.Instructions });
  193. //Contact Information
  194. ProcessProperty(ref metaData, properties.Contact, new[] { iptc.WriterEditor, xmp.CaptionWriter });
  195. var dmsConverter = new RationalToDmsConverter();
  196. var coordinates = new Coordinates();
  197. double latLong;
  198. string reference;
  199. double[] dms;
  200. //Latitude
  201. if (Double.TryParse(properties.GpsLatitude, out latLong) &&
  202. (Math.Abs(latLong) >= 0 && Math.Abs(latLong) <= 90))
  203. {
  204. dms = coordinates.ConvertToDms(Math.Abs(latLong));
  205. reference = latLong > 0D ? "N" : "S";
  206. metaData.SetQuery(exif.GpsLatitude, dmsConverter.ConvertBack(dms));
  207. metaData.SetQuery(exif.GpsLatitudeRef, reference);
  208. metaData.SetQuery(xmp.GpsLatitude, (
  209. dms[0] + "," +
  210. dms[1] + "," +
  211. dms[2] + reference));
  212. }
  213. else
  214. {
  215. DeletePathIfExist();
  216. }
  217. //Longitude
  218. if (Double.TryParse(properties.GpsLongitude, out latLong) &&
  219. (Math.Abs(latLong) >= 0 && Math.Abs(latLong) <= 180))
  220. {
  221. dms = coordinates.ConvertToDms(Math.Abs(latLong));
  222. reference = latLong < 0D ? "W" : "E";
  223. metaData.SetQuery(exif.GpsLongitude, dmsConverter.ConvertBack(dms));
  224. metaData.SetQuery(exif.GpsLongitudeRef, reference);
  225. metaData.SetQuery(xmp.GpsLongitude, (
  226. dms[0] + "," +
  227. dms[1] + "," +
  228. dms[2] + reference));
  229. }
  230. else
  231. {
  232. DeletePathIfExist();
  233. }
  234. //Datum
  235. ProcessProperty(ref metaData, properties.GpsDatum, new[] { exif.GpsMapDatum, xmp.GpsMapDatum });
  236. //Image Direction
  237. if (Double.TryParse(properties.GpsImgDirection, out latLong) &&
  238. (Math.Abs(latLong) >= 0 && Math.Abs(latLong) <= 359.99))
  239. {
  240. dms = coordinates.ConvertToDms(Math.Abs(latLong));
  241. metaData.SetQuery(exif.GpsImgDirection, dmsConverter.ConvertBack(dms));
  242. metaData.SetQuery(xmp.GpsImgDirection, dms[0].ToString());
  243. }
  244. //Image Direction Reference
  245. if (properties.GpsImgDirectionRef.Equals("T", StringComparison.CurrentCultureIgnoreCase) ||
  246. properties.GpsImgDirectionRef.Equals("M", StringComparison.CurrentCultureIgnoreCase))
  247. ProcessProperty(ref metaData, properties.GpsImgDirectionRef.ToUpper(), new[] { exif.GpsImgDirectionRef, xmp.GpsImgDirectionRef });
  248. #endregion Values Assignment
  249. BitmapFrame frame = BitmapFrame.Create(decoder.Frames[0], decoder.Frames[0].Thumbnail, metaData, decoder.Frames[0].ColorContexts);
  250. //adds the frame with new metadata to the encoder
  251. encoder.Frames.Add(frame);
  252. using (Stream tempFileStream = File.Open(tempFile, FileMode.Create, FileAccess.ReadWrite))
  253. {
  254. //saves the image to the temporary file's stream
  255. encoder.Save(tempFileStream);
  256. }
  257. //release the resources
  258. originalFileStream.Dispose(); //releases the lock on original file enabling to overwrite it
  259. //overwrite the original file with the contents of temp file
  260. File.Copy(tempFile, Path, true);
  261. //delete the temporary file
  262. File.Delete(tempFile);
  263. }
  264. #endregion Metadata processing
  265. }
  266. }
  267. ErrorMessage = string.Empty;
  268. //flag metadata has been written sucessfully
  269. IsWriteSuccessfull = true;
  270. }
  271. catch (Exception ex)
  272. {
  273. ErrorMessage = ex.Message;
  274. //flag error occured during process
  275. IsWriteSuccessfull = false;
  276. }
  277. return IsWriteSuccessfull;
  278. }
  279. /// <summary>
  280. /// Reads metadata properties from the image file.
  281. /// </summary>
  282. /// <param name="properties">IImageProperties instance to which metadata values have to be assigned.</param>
  283. /// <returns>Boolean value indicating whether or not read succeeded.</returns>
  284. public override bool TryRead(out IImageProperties properties)
  285. {
  286. try
  287. {
  288. if (!IsPathValid())
  289. {
  290. properties = null;
  291. return IsReadSuccessfull;
  292. }
  293. using (Stream imageFileStream = File.Open(Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
  294. {
  295. //byte array to store the contents of image file
  296. var buffer = new byte[imageFileStream.Length];
  297. //read the imagefile into the byte array
  298. imageFileStream.Read(buffer, 0, (int)imageFileStream.Length);
  299. //create a memory stream with the bytes read
  300. using (var memoryStream = new MemoryStream(buffer))
  301. {
  302. BitmapSource image = BitmapFrame.Create(memoryStream);
  303. var metaData = (BitmapMetadata)image.Metadata;
  304. #region Read Metadata Properties
  305. string imageFormat = System.IO.Path.GetExtension(Path);
  306. //read properties into the corresponding ImageDocument properties
  307. const StringComparison compareOption = StringComparison.CurrentCultureIgnoreCase;
  308. string format = string.Empty;
  309. if (imageFormat != null)
  310. if (imageFormat.Equals(".jpg", compareOption) | imageFormat.Equals("jpeg", compareOption))
  311. format = "JPEG";
  312. else if (imageFormat.Equals(".tif", compareOption) | imageFormat.Equals("tiff", compareOption))
  313. format = "TIFF";
  314. var xmp = new XmpStandard(format);
  315. var iptc = new IptcStandard(format);
  316. var exif = new ExifStandard(format);
  317. var ifd = new IfdStandard(format);
  318. properties = new ImageProperties();
  319. if (metaData != null)
  320. {
  321. properties.Path = Path;
  322. properties.Title = metaData.Title;
  323. properties.Comments = metaData.Comment;
  324. properties.CopyrightNotice = metaData.Copyright;
  325. properties.Authors = GetListItemsAsString(metaData.Author);
  326. properties.Keywords = GetListItemsAsString(metaData.Keywords);
  327. properties.Subject = GetPrioritizedValue(metaData,
  328. new[]
  329. {
  330. xmp.Description, iptc.Headline, exif.ImageDescription,
  331. ifd.Subject
  332. }).ToString();
  333. properties.City = GetPrioritizedValue(metaData,
  334. new[] { xmp.City, iptc.City }).ToString();
  335. properties.Location = GetPrioritizedValue(metaData,
  336. new[] { xmp.Instructions, iptc.SpecialInstructions }).ToString();
  337. properties.Contact = GetPrioritizedValue(metaData,
  338. new[] { xmp.CaptionWriter, iptc.WriterEditor }).ToString();
  339. //convert date to short date format.
  340. //If conversion fails the value is shown as it is.
  341. DateTime dateTaken;
  342. properties.Date = DateTime.TryParse(metaData.DateTaken, out dateTaken)
  343. ? dateTaken.ToShortDateString()
  344. : metaData.DateTaken;
  345. string access = GetPrioritizedValue(metaData,
  346. new[] { xmp.Urgency, iptc.Urgency }).ToString();
  347. int accessLevel;
  348. accessLevel = Int32.TryParse(access, out accessLevel) ? accessLevel : 8;
  349. properties.Urgency = (Urgency)accessLevel;
  350. properties.GpsDatum = GetPrioritizedValue(metaData,
  351. new[] { exif.GpsMapDatum, xmp.GpsMapDatum }).ToString();
  352. properties.GpsLatitude = GetCoordinateValues(
  353. GetPrioritizedValue(metaData, new[] { exif.GpsLatitude }),
  354. GetPrioritizedValue(metaData, new[] { exif.GpsLatitudeRef }).ToString(),
  355. GetPrioritizedValue(metaData, new[] { xmp.GpsLatitude }));
  356. properties.GpsLongitude = GetCoordinateValues(
  357. GetPrioritizedValue(metaData, new[] { exif.GpsLongitude }),
  358. GetPrioritizedValue(metaData, new[] { exif.GpsLongitudeRef }).ToString(),
  359. GetPrioritizedValue(metaData, new[] { xmp.GpsLongitude }));
  360. properties.GpsImgDirection = ConvertRationalToDecimalDegrees(GetPrioritizedValue(metaData, new[] { exif.GpsImgDirection, xmp.GpsImgDirection })).ToString();
  361. if (!string.IsNullOrEmpty(properties.GpsImgDirection))
  362. properties.GpsImgDirection += Utility.DegreeSymbol;
  363. properties.GpsImgDirectionRef = GetPrioritizedValue(metaData, new[] { exif.GpsImgDirectionRef, xmp.GpsImgDirectionRef }).ToString();
  364. }
  365. #endregion Read Metadata Properties
  366. }
  367. }
  368. ErrorMessage = string.Empty;
  369. //flag metadata has been read sucessfully
  370. IsReadSuccessfull = true;
  371. }
  372. catch (Exception ex)
  373. {
  374. properties = null;
  375. ErrorMessage = ex.Message;
  376. //flag error occured while reading
  377. IsReadSuccessfull = false;
  378. }
  379. return IsReadSuccessfull;
  380. }
  381. /// <summary>
  382. /// Checks if the format of the image file is currently supported.
  383. /// </summary>
  384. /// <returns>Boolean value indicating wheather or not image format is supported.</returns>
  385. public bool IsSupportedFormat()
  386. {
  387. var supportedFormats = new List<string> { ".jpg", ".jpeg", ".tiff", ".tif" };
  388. return IsSupportedFormat(supportedFormats);
  389. }
  390. #endregion
  391. #endregion public methods
  392. #region private methods
  393. /// <summary>
  394. /// Gets the prioritized property value based on the Photo Metadata Policy.
  395. /// </summary>
  396. /// <param name="metaData">Instance of metadata</param>
  397. /// <param name="queries">Queries from which to get value. Must be arranged in their order of precedence.</param>
  398. /// <returns></returns>
  399. private static object GetPrioritizedValue(BitmapMetadata metaData, IEnumerable<string> queries)
  400. {
  401. //Rule for reading a property value. This is inline with Microsoft's Photo Metadata Policy.
  402. //Order of precedence(based on their existence) : XMP , IPTC, EXIF, IFD
  403. //If XMP exists it is returned. If not it looks for IPTC and returns if it exists. If not looks for EXIF and then IFD.
  404. object prioritizedValue = string.Empty;
  405. foreach (string t in queries.Where(metaData.ContainsQuery))
  406. {
  407. prioritizedValue = metaData.GetQuery(t);
  408. break;
  409. }
  410. //Properties modified using XP summary properties are stored as byte arrays in IFD.
  411. if (prioritizedValue is byte[])
  412. prioritizedValue = Encoding.ASCII.GetString((byte[])prioritizedValue).Trim('\0');
  413. else if (prioritizedValue is BitmapMetadataBlob)//EXIF softwares write some properties as BitmapMetadataBlob
  414. prioritizedValue = Encoding.UTF8.GetString(((BitmapMetadataBlob)prioritizedValue).GetBlobValue()).Trim('\0');
  415. //else if (prioritizedValue is List<)
  416. return prioritizedValue;
  417. }
  418. private static string GetListItemsAsString(IEnumerable<string> list)
  419. {
  420. string stringValue = string.Empty;
  421. if (list != null)
  422. stringValue = list
  423. .Aggregate(stringValue, (current, item) => current + (string.IsNullOrEmpty(item) ? string.Empty : item + ";"));
  424. return stringValue;
  425. }
  426. private static double ConvertRationalToDecimalDegrees(object value)
  427. {
  428. if (value is ulong[])
  429. {
  430. var converter = new RationalToDmsConverter();
  431. var result = (double)converter.Convert(value);
  432. return result;
  433. }
  434. return 0;
  435. }
  436. private static string GetCoordinateValues(object exifLatLong, string exifRef, object xmpLatLong)
  437. {
  438. const StringComparison compareOption = StringComparison.CurrentCultureIgnoreCase;
  439. double reference, latLong;
  440. if (exifLatLong is ulong[])
  441. {
  442. reference = ((exifRef.Equals("N", compareOption)) || (exifRef.Equals("E", compareOption)))
  443. ? 1 : -1;
  444. latLong = ConvertRationalToDecimalDegrees(exifLatLong);
  445. latLong = latLong * reference;
  446. return latLong + Utility.DegreeSymbol;
  447. }
  448. if (!string.IsNullOrEmpty(xmpLatLong.ToString()))
  449. {
  450. //Matches “DDD,MM,SSk” or “DDD,MM.mmk” pattern where
  451. //DDD is a number of degrees,MM is a number of minutes,SS is a number of seconds
  452. //mm is a fraction of minutes,k is a single character N, S, E, or W indicating a direction (north, south, east, west)
  453. const string pattern = @"^[0-9]{1,3},[0-9]{1,2}(,[0-9]{1,2}|\.[0-9]+)[NnSsEeWw]$";
  454. if (Regex.IsMatch(xmpLatLong.ToString(), pattern))
  455. {
  456. var coordinates = new Coordinates();
  457. string[] values = xmpLatLong.ToString().Split(new[] { ',' });
  458. string lastWord = values[values.Length - 1];
  459. string rfrnc = lastWord.Substring(lastWord.Length - 1);
  460. values[values.Length - 1] = lastWord.TrimEnd(rfrnc.ToCharArray());
  461. var latLongD = new double[values.Length];
  462. for (int i = 0; i < values.Length; i++)
  463. {
  464. latLongD[i] = Double.Parse(values[i]);
  465. }
  466. reference = ((rfrnc.Equals("N", compareOption)) || (rfrnc.Equals("E", compareOption)))
  467. ? 1 : -1;
  468. latLong = coordinates.ConvertToDecimalDegrees(latLongD) * reference;
  469. return latLong + Utility.DegreeSymbol;
  470. }
  471. return xmpLatLong.ToString();
  472. }
  473. if (!string.IsNullOrEmpty(exifLatLong.ToString()))
  474. return exifLatLong + exifRef;
  475. return string.Empty;
  476. }
  477. private static void CreatePathIfNotExist(ref BitmapMetadata metaData, IEnumerable<string> paths)
  478. {
  479. foreach (string path in paths)
  480. {
  481. if (!metaData.ContainsQuery(path))
  482. metaData.SetQuery(path, string.Empty);
  483. }
  484. }
  485. private static void DeletePathIfExist()
  486. {
  487. return;
  488. //commented on 12/5/2010
  489. //not required to delete paths that donot have values.
  490. //foreach (string path in paths)
  491. //{
  492. // if (metaData.ContainsQuery(path))
  493. // metaData.RemoveQuery(path);
  494. //}
  495. }
  496. private static void ProcessProperty(ref BitmapMetadata metaData, string propertyValue, IEnumerable<string> paths)
  497. {
  498. if (string.IsNullOrEmpty(propertyValue))
  499. DeletePathIfExist();
  500. else
  501. {
  502. foreach (string path in paths)
  503. {
  504. metaData.SetQuery(path, propertyValue);
  505. }
  506. }
  507. }
  508. private static void ProcessProperty(ref BitmapMetadata metaData, string propertyValue, string property, IEnumerable<string> paths)
  509. {
  510. if (string.IsNullOrEmpty(propertyValue))
  511. DeletePathIfExist();
  512. else
  513. {
  514. CreatePathIfNotExist(ref metaData, paths);
  515. metaData.SetQuery(property, propertyValue);
  516. }
  517. }
  518. #endregion private methods
  519. }
  520. }