1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33:
34: $plugin_is_filter = 9 | CLASS_PLUGIN;
35: $plugin_description = gettext('Extracts <em>XMP</em> metadata from images and <code>XMP</code> sidecar files.');
36: $plugin_author = "Stephen Billard (sbillard)";
37:
38: $option_interface = 'xmpMetadata';
39:
40: zp_register_filter('album_instantiate', 'xmpMetadata::album_instantiate');
41: zp_register_filter('new_album', 'xmpMetadata::new_album');
42: zp_register_filter('album_refresh', 'xmpMetadata::new_album');
43: zp_register_filter('image_instantiate', 'xmpMetadata::image_instantiate');
44: zp_register_filter('image_metadata', 'xmpMetadata::new_image');
45: zp_register_filter('upload_filetypes', 'xmpMetadata::sidecars');
46: zp_register_filter('save_album_utilities_data', 'xmpMetadata::putXMP');
47: zp_register_filter('edit_album_utilities', 'xmpMetadata::create');
48: zp_register_filter('save_image_utilities_data', 'xmpMetadata::putXMP');
49: zp_register_filter('edit_image_utilities', 'xmpMetadata::create');
50: zp_register_filter('bulk_image_actions', 'xmpMetadata::bulkActions');
51: zp_register_filter('bulk_album_actions', 'xmpMetadata::bulkActions');
52:
53: require_once(dirname(dirname(__FILE__)) . '/exif/exif.php');
54:
55: define('XMP_EXTENSION', strtolower(getOption('xmpMetadata_suffix')));
56:
57: 58: 59: 60:
61: class xmpMetadata {
62:
63: private static $XML_trans = array(
64: '€' => '€',
65: '‚' => '‚',
66: 'ƒ' => 'ƒ',
67: '„' => '„',
68: '…' => '…',
69: '†' => '†',
70: '‡' => '‡',
71: 'ˆ' => 'ˆ',
72: '‰' => '‰',
73: 'Š' => 'Š',
74: '‹' => '‹',
75: 'Œ' => 'Œ',
76: 'Ž' => 'Ž',
77: '‘' => '‘',
78: '’' => '’',
79: '“' => '“',
80: '”' => '”',
81: '•' => '•',
82: '–' => '–',
83: '—' => '—',
84: '˜' => '˜',
85: '™' => '™',
86: 'š' => 'š',
87: '›' => '›',
88: 'œ' => 'œ',
89: 'ž' => 'ž',
90: 'Ÿ' => 'Ÿ',
91: '¡' => '¡',
92: '¢' => '¢',
93: '£' => '£',
94: '¤' => '¤',
95: '¥' => '¥',
96: '¦' => '¦',
97: '§' => '§',
98: '¨' => '¨',
99: '©' => '©',
100: 'ª' => 'ª',
101: '«' => '«',
102: '¬' => '¬',
103: '­' => '',
104: '®' => '®',
105: '¯' => '¯',
106: '°' => '°',
107: '±' => '±',
108: '²' => '²',
109: '³' => '³',
110: '´' => '´',
111: 'µ' => 'µ',
112: '¶' => '¶',
113: '·' => '·',
114: '¸' => '¸',
115: '¹' => '¹',
116: 'º' => 'º',
117: '»' => '»',
118: '¼' => '¼',
119: '½' => '½',
120: '¾' => '¾',
121: '¿' => '¿',
122: 'À' => 'À',
123: 'Á' => 'Á',
124: 'Â' => 'Â',
125: 'Ã' => 'Ã',
126: 'Ä' => 'Ä',
127: 'Å' => 'Å',
128: 'Æ' => 'Æ',
129: 'Ç' => 'Ç',
130: 'È' => 'È',
131: 'É' => 'É',
132: 'Ê' => 'Ê',
133: 'Ë' => 'Ë',
134: 'Ì' => 'Ì',
135: 'Í' => 'Í',
136: 'Î' => 'Î',
137: 'Ï' => 'Ï',
138: 'Ð' => 'Ð',
139: 'Ñ' => 'Ñ',
140: 'Ò' => 'Ò',
141: 'Ó' => 'Ó',
142: 'Ô' => 'Ô',
143: 'Õ' => 'Õ',
144: 'Ö' => 'Ö',
145: '×' => '×',
146: 'Ø' => 'Ø',
147: 'Ù' => 'Ù',
148: 'Ú' => 'Ú',
149: 'Û' => 'Û',
150: 'Ü' => 'Ü',
151: 'Ý' => 'Ý',
152: 'Þ' => 'Þ',
153: 'ß' => 'ß',
154: 'à' => 'à',
155: 'á' => 'á',
156: 'â' => 'â',
157: 'ã' => 'ã',
158: 'ä' => 'ä',
159: 'å' => 'å',
160: 'æ' => 'æ',
161: 'ç' => 'ç',
162: 'è' => 'è',
163: 'é' => 'é',
164: 'ê' => 'ê',
165: 'ë' => 'ë',
166: 'ì' => 'ì',
167: 'í' => 'í',
168: 'î' => 'î',
169: 'ï' => 'ï',
170: 'ð' => 'ð',
171: 'ñ' => 'ñ',
172: 'ò' => 'ò',
173: 'ó' => 'ó',
174: 'ô' => 'ô',
175: 'õ' => 'õ',
176: 'ö' => 'ö',
177: '÷' => '÷',
178: 'ø' => 'ø',
179: 'ù' => 'ù',
180: 'ú' => 'ú',
181: 'û' => 'û',
182: 'ü' => 'ü',
183: 'ý' => 'ý',
184: 'þ' => 'þ',
185: 'ÿ' => 'ÿ',
186: 'Ā' => 'Ā',
187: 'ā' => 'ā',
188: 'Ă' => 'Ă',
189: 'ă' => 'ă',
190: 'Ą' => 'Ą',
191: 'ą' => 'ą',
192: 'Ć' => 'Ć',
193: 'ć' => 'ć',
194: 'Ĉ' => 'Ĉ',
195: 'ĉ' => 'ĉ',
196: 'Ċ' => 'Ċ',
197: 'ċ' => 'ċ',
198: 'Č' => 'Č',
199: 'č' => 'č',
200: 'Ď' => 'Ď',
201: 'ď' => 'ď',
202: 'Đ' => 'Đ',
203: 'đ' => 'đ',
204: 'Ē' => 'Ē',
205: 'ē' => 'ē',
206: 'Ĕ' => 'Ĕ',
207: 'ĕ' => 'ĕ',
208: 'Ė' => 'Ė',
209: 'ė' => 'ė',
210: 'Ę' => 'Ę',
211: 'ę' => 'ę',
212: 'Ě' => 'Ě',
213: 'ě' => 'ě',
214: 'Ĝ' => 'Ĝ',
215: 'ĝ' => 'ĝ',
216: 'Ğ' => 'Ğ',
217: 'ğ' => 'ğ',
218: 'Ġ' => 'Ġ',
219: 'ġ' => 'ġ',
220: 'Ģ' => 'Ģ',
221: 'ģ' => 'ģ',
222: 'Ĥ' => 'Ĥ',
223: 'ĥ' => 'ĥ',
224: 'Ħ' => 'Ħ',
225: 'ħ' => 'ħ',
226: 'Ĩ' => 'Ĩ',
227: 'ĩ' => 'ĩ',
228: 'Ī' => 'Ī',
229: 'ī' => 'ī',
230: 'Ĭ' => 'Ĭ',
231: 'ĭ' => 'ĭ',
232: 'Į' => 'Į',
233: 'į' => 'į',
234: 'İ' => 'İ',
235: 'ı' => 'ı',
236: 'IJ' => 'IJ',
237: 'ij' => 'ij',
238: 'Ĵ' => 'Ĵ',
239: 'ĵ' => 'ĵ',
240: 'Ķ' => 'Ķ',
241: 'ķ' => 'ķ',
242: 'ĸ' => 'ĸ',
243: 'Ĺ' => 'Ĺ',
244: 'ĺ' => 'ĺ',
245: 'Ļ' => 'Ļ',
246: 'ļ' => 'ļ',
247: 'Ľ' => 'Ľ',
248: 'ľ' => 'ľ',
249: 'Ŀ' => 'Ŀ',
250: 'ŀ' => 'ŀ',
251: 'Ł' => 'Ł',
252: 'ł' => 'ł',
253: 'Ń' => 'Ń',
254: 'ń' => 'ń',
255: 'Ņ' => 'Ņ',
256: 'ņ' => 'ņ',
257: 'Ň' => 'Ň',
258: 'ň' => 'ň',
259: 'ʼn' => 'ʼn',
260: 'Ŋ' => 'Ŋ',
261: 'ŋ' => 'ŋ',
262: 'Ō' => 'Ō',
263: 'ō' => 'ō',
264: 'Ŏ' => 'Ŏ',
265: 'ŏ' => 'ŏ',
266: 'Ő' => 'Ő',
267: 'ő' => 'ő',
268: 'Œ' => 'Œ',
269: 'œ' => 'œ',
270: 'Ŕ' => 'Ŕ',
271: 'ŕ' => 'ŕ',
272: 'Ŗ' => 'Ŗ',
273: 'ŗ' => 'ŗ',
274: 'Ř' => 'Ř',
275: 'ř' => 'ř',
276: 'Ś' => 'Ś',
277: 'ś' => 'ś',
278: 'Ŝ' => 'Ŝ',
279: 'ŝ' => 'ŝ',
280: '"' => '"',
281: 'Ş' => 'Ş',
282: 'ş' => 'ş',
283: 'Š' => 'Š',
284: 'š' => 'š',
285: 'Ţ' => 'Ţ',
286: 'ţ' => 'ţ',
287: 'Ť' => 'Ť',
288: 'ť' => 'ť',
289: 'Ŧ' => 'Ŧ',
290: 'ŧ' => 'ŧ',
291: 'Ũ' => 'Ũ',
292: 'ũ' => 'ũ',
293: 'Ū' => 'Ū',
294: 'ū' => 'ū',
295: 'Ŭ' => 'Ŭ',
296: 'ŭ' => 'ŭ',
297: 'Ů' => 'Ů',
298: 'ů' => 'ů',
299: 'Ű' => 'Ű',
300: 'ű' => 'ű',
301: 'Ų' => 'Ų',
302: 'ų' => 'ų',
303: 'Ŵ' => 'Ŵ',
304: 'ŵ' => 'ŵ',
305: 'Ŷ' => 'Ŷ',
306: 'ŷ' => 'ŷ',
307: 'Ź' => 'Ź',
308: 'ź' => 'ź',
309: 'Ż' => 'Ż',
310: 'ż' => 'ż',
311: 'Ž' => 'Ž',
312: 'ž' => 'ž',
313: 'ſ' => 'ſ',
314: '&' => '&',
315: ''' => '\'',
316: 'ƒ' => 'ƒ',
317: 'Ʒ' => 'Ʒ',
318: 'DŽ' => 'DŽ',
319: 'Dž' => 'Dž',
320: 'dž' => 'dž',
321: 'LJ' => 'LJ',
322: 'Lj' => 'Lj',
323: 'lj' => 'lj',
324: 'NJ' => 'NJ',
325: 'Nj' => 'Nj',
326: 'nj' => 'nj',
327: 'Ǟ' => 'Ǟ',
328: 'ǟ' => 'ǟ',
329: 'Ǥ' => 'Ǥ',
330: 'ǥ' => 'ǥ',
331: 'Ǧ' => 'Ǧ',
332: 'ǧ' => 'ǧ',
333: 'Ǩ' => 'Ǩ',
334: 'ǩ' => 'ǩ',
335: 'Ǯ' => 'Ǯ',
336: 'ǯ' => 'ǯ',
337: 'DZ' => 'DZ',
338: 'dz' => 'dz',
339: 'Ǵ' => 'Ǵ',
340: 'ǵ' => 'ǵ',
341: 'Ǻ' => 'Ǻ',
342: 'ǻ' => 'ǻ',
343: 'Ǽ' => 'Ǽ',
344: 'ǽ' => 'ǽ',
345: 'Ǿ' => 'Ǿ',
346: 'ǿ' => 'ǿ',
347: '<' => '<',
348: '>' => '>',
349: 'ɼ' => 'ɼ',
350: 'fi' => 'fi',
351: 'fl' => 'fl',
352: 'ʒ' => 'ʒ',
353: '˘' => '˘',
354: '˙' => '˙',
355: '˚' => '˚',
356: '˛' => '˛',
357: '˜' => '˜',
358: '˝' => '˝',
359: 'Ḃ' => 'Ḃ',
360: 'ḃ' => 'ḃ',
361: 'Ḋ' => 'Ḋ',
362: 'ḋ' => 'ḋ',
363: 'Ḑ' => 'Ḑ',
364: 'ḑ' => 'ḑ',
365: 'Ḟ' => 'Ḟ',
366: 'ḟ' => 'ḟ',
367: 'Ḱ' => 'Ḱ',
368: 'ḱ' => 'ḱ',
369: 'Ṁ' => 'Ṁ',
370: 'ṁ' => 'ṁ',
371: 'Ṗ' => 'Ṗ',
372: 'ṗ' => 'ṗ',
373: 'Ṡ' => 'Ṡ',
374: 'ṡ' => 'ṡ',
375: 'Ṫ' => 'Ṫ',
376: 'ṫ' => 'ṫ',
377: 'Ẁ' => 'Ẁ',
378: 'ẁ' => 'ẁ',
379: 'Ẃ' => 'Ẃ',
380: 'ẃ' => 'ẃ',
381: 'Ẅ' => 'Ẅ',
382: 'ẅ' => 'ẅ',
383: 'Ỳ' => 'Ỳ',
384: 'ỳ' => 'ỳ',
385: '―' => '―',
386: '‣' => '‣',
387: '‼' => '‼',
388: '‾' => '‾',
389: '⁄' => '⁄',
390: 'ⁿ' => 'ⁿ',
391: '₣' => '₣',
392: '₤' => '₤',
393: '₧' => '₧',
394: '℅' => '℅',
395: '№' => '№',
396: '⅛' => '⅛',
397: '⅜' => '⅜',
398: '⅝' => '⅝',
399: '⅞' => '⅞',
400: '←' => '←',
401: '↑' => '↑',
402: '→' => '→',
403: '↓' => '↓',
404: '∂' => '∂',
405: '∆' => '∆',
406: '∏' => '∏',
407: '∑' => '∑',
408: '∙' => '∙',
409: '√' => '√',
410: '∞' => '∞',
411: '∟' => '∟',
412: '∩' => '∩',
413: '∫' => '∫',
414: '≈' => '≈',
415: '≠' => '≠',
416: '≡' => '≡',
417: '≤' => '≤',
418: '≥' => '≥',
419: '^' => '^',
420: '♀' => '♀',
421: '♂' => '♂',
422: '♠' => '♠',
423: '♣' => '♣',
424: '♥' => '♥',
425: '♦' => '♦',
426: '♩' => '♩',
427: '♪' => '♪',
428: '♬' => '♬',
429: '♭' => '♭',
430: '♯' => '♯',
431: '&498;' => 'Dz',
432: 'Æ' => 'Æ',
433: 'Á' => 'Á',
434: 'Â' => 'Â',
435: 'À' => 'À',
436: 'Å' => 'Å',
437: 'Ã' => 'Ã',
438: 'Ä' => 'Ä',
439: 'Ç' => 'Ç',
440: '‡' => '‡',
441: 'Ð' => 'Ð',
442: 'É' => 'É',
443: 'Ê' => 'Ê',
444: 'È' => 'È',
445: 'Ë' => 'Ë',
446: 'Í' => 'Í',
447: 'Î' => 'Î',
448: 'Ì' => 'Ì',
449: 'Ï' => 'Ï',
450: 'Ñ' => 'Ñ',
451: 'Œ' => 'Œ',
452: 'Ó' => 'Ó',
453: 'Ô' => 'Ô',
454: 'Ò' => 'Ò',
455: 'Ø' => 'Ø',
456: 'Õ' => 'Õ',
457: 'Ö' => 'Ö',
458: 'Þ' => 'Þ',
459: 'Ú' => 'Ú',
460: 'Û' => 'Û',
461: 'Ù' => 'Ù',
462: 'Ü' => 'Ü',
463: 'Ý' => 'Ý',
464: 'Ÿ' => 'Ÿ',
465: 'á' => 'á',
466: 'â' => 'â',
467: '´' => '´',
468: 'æ' => 'æ',
469: 'à' => 'à',
470: '&' => '&',
471: 'å' => 'å',
472: 'ã' => 'ã',
473: 'ä' => 'ä',
474: '¦' => '¦',
475: 'ç' => 'ç',
476: '¸' => '¸',
477: '¢' => '¢',
478: '♣' => '♣',
479: '©' => '©',
480: '¤' => '¤',
481: '†' => '†',
482: '↓' => '↓',
483: '&dbquo;' => '„',
484: '°' => '°',
485: '♦' => '♦',
486: '÷' => '÷',
487: 'é' => 'é',
488: 'ê' => 'ê',
489: 'è' => 'è',
490: 'ð' => 'ð',
491: 'ë' => 'ë',
492: '€' => '€',
493: '½' => '½',
494: '¼' => '¼',
495: '¾' => '¾',
496: '>' => '>',
497: '♥' => '♥',
498: 'í' => 'í',
499: 'î' => 'î',
500: '¡' => '¡',
501: 'ì' => 'ì',
502: '¿' => '¿',
503: 'ï' => 'ï',
504: '«' => '«',
505: '←' => '←',
506: '“' => '“',
507: '‹' => '‹',
508: '‘' => '‘',
509: '<' => '<',
510: '¯' => '¯',
511: '—' => '—',
512: 'µ' => 'µ',
513: '·' => '·',
514: '–' => '–',
515: '¬' => '¬',
516: 'ñ' => 'ñ',
517: 'ó' => 'ó',
518: 'ô' => 'ô',
519: 'œ' => 'œ',
520: 'ò' => 'ò',
521: '‾' => '‾',
522: 'ª' => 'ª',
523: 'º' => 'º',
524: 'ø' => 'ø',
525: 'õ' => 'õ',
526: 'ö' => 'ö',
527: '¶' => '¶',
528: '‰' => '‰',
529: '±' => '±',
530: '£' => '£',
531: '"' => '"',
532: '»' => '»',
533: '→' => '→',
534: '”' => '”',
535: '®' => '®',
536: '›' => '›',
537: '’' => '’',
538: '‚' => '‚',
539: '§' => '§',
540: '­' => '',
541: '♠' => '♠',
542: '¹' => '¹',
543: '²' => '²',
544: '³' => '³',
545: 'ß' => 'ß',
546: 'þ' => 'þ',
547: '&tilde' => '˜',
548: '˜' => '˜',
549: '×' => '×',
550: '™' => '™',
551: 'ú' => 'ú',
552: '↑' => '↑',
553: 'û' => 'û',
554: 'ù' => 'ù',
555: '¨' => '¨',
556: 'ü' => 'ü',
557: 'ý' => 'ý',
558: '¥' => '¥',
559: 'ÿ' => 'ÿ'
560: );
561:
562: 563: 564: 565: 566:
567: function __construct() {
568: setOptionDefault('xmpMetadata_suffix', 'xmp');
569: }
570:
571: 572: 573: 574: 575:
576: function getOptionsSupported() {
577: global $_zp_supported_images, $_zp_extra_filetypes;
578: $list = $_zp_supported_images;
579: foreach (array('gif', 'wbmp') as $suffix) {
580: $key = array_search($suffix, $list);
581: if ($key !== false)
582: unset($list[$key]);
583: }
584: natcasesort($list);
585: $types = array();
586: foreach ($_zp_extra_filetypes as $suffix => $type) {
587: if ($type == 'Video')
588: $types[] = $suffix;
589: }
590: natcasesort($types);
591: $list = array_merge($list, $types);
592: $listi = array();
593: foreach ($list as $suffix) {
594: $listi[$suffix] = 'xmpMetadata_examine_images_' . $suffix;
595: }
596: return array(gettext('Sidecar file extension') => array('key' => 'xmpMetadata_suffix', 'type' => OPTION_TYPE_TEXTBOX,
597: 'desc' => gettext('The plugin will look for files with <em>image_name.extension</em> and extract XMP metadata from them into the <em>image_name</em> record.')),
598: gettext('Process extensions') => array('key' => 'xmpMetadata_examine_imagefile', 'type' => OPTION_TYPE_CHECKBOX_UL,
599: 'checkboxes' => $listi,
600: 'desc' => gettext('If no sidecar file exists and the extension is enabled, the plugin will search within that type <em>image</em> file for an <code>XMP</code> block. <strong>Warning</strong> do not set this option unless you require it. Searching image files can be computationally intensive.'))
601: );
602: }
603:
604: 605: 606: 607: 608: 609:
610: function handleOption($option, $currentValue) {
611:
612: }
613:
614: 615: 616: 617: 618: 619:
620: private static function extract($xmpdata) {
621: $desiredtags = array(
622: 'EXIFLensType' => '<aux:Lens>',
623: 'EXIFLensInfo' => '<aux:LensInfo>',
624: 'EXIFArtist' => '<dc:creator>',
625: 'IPTCCopyright' => '<dc:rights>',
626: 'IPTCImageCaption' => '<dc:description>',
627: 'IPTCObjectName' => '<dc:title>',
628: 'IPTCKeywords' => '<dc:subject>',
629: 'EXIFExposureTime' => '<exif:ExposureTime>',
630: 'EXIFFNumber' => '<exif:FNumber>',
631: 'EXIFAperatureValue' => '<exif:ApertureValue>',
632: 'EXIFExposureProgram' => '<exif:ExposureProgram>',
633: 'EXIFISOSpeedRatings' => '<exif:ISOSpeedRatings>',
634: 'EXIFDateTimeOriginal' => '<exif:DateTimeOriginal>',
635: 'EXIFExposureBiasValue' => '<exif:ExposureBiasValue>',
636: 'EXIFGPSLatitude' => '<exif:GPSLatitude>',
637: 'EXIFGPSLongitude' => '<exif:GPSLongitude>',
638: 'EXIFGPSAltitude' => '<exif:GPSAltitude>',
639: 'EXIFGPSAltituedRef' => '<exif:GPSAltitudeRef>',
640: 'EXIFMeteringMode' => '<exif:MeteringMode>',
641: 'EXIFFocalLength' => '<exif:FocalLength>',
642: 'EXIFContrast' => '<exif:Contrast>',
643: 'EXIFSharpness' => '<exif:Sharpness>',
644: 'EXIFExposureTime' => '<exif:ShutterSpeedValue>',
645: 'EXIFSaturation' => '<exif:Saturation>',
646: 'EXIFWhiteBalance' => '<exif:WhiteBalance>',
647: 'IPTCLocationCode' => '<Iptc4xmpCore:CountryCode>',
648: 'IPTCSubLocation' => '<Iptc4xmpCore:Location>',
649: 'rating' => '<MicrosoftPhoto:Rating>',
650: 'IPTCSource' => '<photoshop:Source>',
651: 'IPTCCity' => '<photoshop:City>',
652: 'IPTCState' => '<photoshop:State>',
653: 'IPTCLocationName' => '<photoshop:Country>',
654: 'IPTCImageHeadline' => '<photoshop:Headline>',
655: 'IPTCImageCredit' => '<photoshop:Credit>',
656: 'EXIFMake' => '<tiff:Make>',
657: 'EXIFModel' => '<tiff:Model>',
658: 'EXIFOrientation' => '<tiff:Orientation>',
659: 'EXIFImageWidth' => '<tiff:ImageWidth>',
660: 'EXIFImageHeight' => '<tiff:ImageLength>',
661: 'owner' => '<zp:Owner>',
662: 'thumb' => '<zp:Thumbnail>',
663: 'watermark' => '<zp:Watermark>',
664: 'watermark_use' => '<zp:Watermark_use>',
665: 'watermark_thumb' => '<zp:Watermark_thumb>',
666: 'custom_data' => '<zp:CustomData',
667: 'codeblock' => '<zp:Codeblock>'
668: );
669: $xmp_parsed = array();
670: while (!empty($xmpdata)) {
671: $s = strpos($xmpdata, '<');
672: $e = strpos($xmpdata, '>', $s);
673: $tag = substr($xmpdata, $s, $e - $s + 1);
674: $xmpdata = substr($xmpdata, $e + 1);
675: $key = array_search($tag, $desiredtags);
676: if ($key !== false) {
677: $close = str_replace('<', '</', $tag);
678: $e = strpos($xmpdata, $close);
679: $meta = trim(substr($xmpdata, 0, $e));
680: $xmpdata = substr($xmpdata, $e + strlen($close));
681: if (strpos($meta, '<') === false) {
682: $xmp_parsed[$key] = self::decode($meta);
683: } else {
684: $elements = array();
685: while (!empty($meta)) {
686: $s = strpos($meta, '<');
687: $e = strpos($meta, '>', $s);
688: $tag = substr($meta, $s, $e - $s + 1);
689: $meta = substr($meta, $e + 1);
690: if (strpos($tag, 'rdf:li') !== false) {
691: $e = strpos($meta, '</rdf:li>');
692: $elements[] = self::decode(trim(substr($meta, 0, $e)));
693: $meta = substr($meta, $e + 9);
694: }
695: }
696: $xmp_parsed[$key] = $elements;
697: }
698: } else {
699: if (strpos($tag, '<rdf:Description') !== false) {
700: $meta = substr($tag, 17);
701: while (preg_match('/^[a-zA-z0-9_]+\:[a-zA-z0-9_]+\=".*"/', $meta, $element)) {
702: $item = $element[0];
703: $meta = trim(substr($meta, strlen($item)));
704: $i = strpos($item, '=');
705: $tag = '<' . substr($item, 0, $i) . '>';
706: $v = self::decode(trim(substr($item, $i + 2, -1)));
707: $key = array_search($tag, $desiredtags);
708: if ($key !== false) {
709: $xmp_parsed[$key] = $v;
710: }
711: }
712: }
713: }
714: }
715: return ($xmp_parsed);
716: }
717:
718: 719: 720: 721: 722: 723:
724: private static function to_string($meta) {
725: if (is_array($meta)) {
726: $meta = implode(',', $meta);
727: }
728: return trim($meta);
729: }
730:
731: 732: 733: 734: 735: 736: 737:
738: static function album_instantiate($album) {
739: $album->sidecars[XMP_EXTENSION] = XMP_EXTENSION;
740: return $album;
741: }
742:
743: 744: 745: 746: 747: 748:
749: static function new_album($album) {
750: $metadata_path = dirname($album->localpath) . '/' . basename($album->localpath) . '*';
751: $files = safe_glob($metadata_path);
752: if (count($files) > 0) {
753: foreach ($files as $file) {
754: if (strtolower(getSuffix($file)) == XMP_EXTENSION) {
755: $source = file_get_contents($file);
756: $metadata = self::extract($source);
757: if (array_key_exists('IPTCImageCaption', $metadata)) {
758: $album->setDesc(self::to_string($metadata['IPTCImageCaption']));
759: }
760: if (array_key_exists('IPTCImageHeadline', $metadata)) {
761: $album->setTitle(self::to_string($metadata['IPTCImageHeadline']));
762: }
763: if (array_key_exists('IPTCLocationName', $metadata)) {
764: $album->setLocation(self::to_string($metadata['IPTCLocationName']));
765: }
766: if (array_key_exists('IPTCKeywords', $metadata)) {
767: $tags = $metadata['IPTCKeywords'];
768: if (!is_array($tags)) {
769: $tags = explode(',', $tags);
770: }
771: $album->setTags($tags);
772: }
773: if (array_key_exists('EXIFDateTimeOriginal', $metadata)) {
774: $album->setDateTime($metadata['EXIFDateTimeOriginal']);
775: }
776: if (array_key_exists('thumb', $metadata)) {
777: $album->setThumb($metadata['thumb']);
778: }
779: if (array_key_exists('owner', $metadata)) {
780: $album->setOwner($metadata['owner']);
781: }
782: if (array_key_exists('custom_data', $metadata)) {
783: $album->setCustomData($metadata['custom_data']);
784: }
785: if (array_key_exists('codeblock', $metadata)) {
786: $album->setCodeblock($metadata['codeblock']);
787: }
788: if (array_key_exists('watermark', $metadata)) {
789: $album->setWatermark($metadata['watermark']);
790: }
791: if (array_key_exists('watermark_thumb', $metadata)) {
792: $album->setWatermarkThumb($metadata['watermark_thumb']);
793: }
794: if (array_key_exists('rating', $metadata)) {
795: $v = min(getoption('rating_stars_count'), $metadata['rating']) * min(1, getOption('rating_split_stars'));
796: $album->set('total_value', $v);
797: $album->set('rating', $v);
798: $album->set('total_votes', 1);
799: }
800: $album->save();
801: break;
802: }
803: }
804: return $album;
805: }
806: }
807:
808: 809: 810: 811: 812: 813:
814: private static function extractXMP($f) {
815: if (preg_match('~<.*?xmpmeta~', $f, $m)) {
816: $open = $m[0];
817: $close = str_replace('<', '</', $open);
818: $j = strpos($f, $open);
819: if ($j !== false) {
820: $k = strpos($f, $close, $j + 4);
821: $meta = substr($f, $j, $k + 14 - $j);
822: $l = 0;
823: return $meta;
824: }
825: }
826: return false;
827: }
828:
829: 830: 831: 832: 833: 834:
835: private static function rationalNum($element) {
836:
837: $n = explode('/', $element);
838: $v = sprintf('%f', $n[0] / $n[1]);
839: for ($i = strlen($v) - 1; $i > 1; $i--) {
840: if ($v{$i} != '0')
841: break;
842: }
843: if ($v{$i} == '.')
844: $i--;
845: return substr($v, 0, $i + 1);
846: }
847:
848: private static function encode($str) {
849: return strtr($str, array_flip(self::$XML_trans));
850: }
851:
852: private static function decode($str) {
853: return strtr($str, self::$XML_trans);
854: }
855:
856: static function image_instantiate($image) {
857: $image->sidecars[XMP_EXTENSION] = XMP_EXTENSION;
858: return $image;
859: }
860:
861: 862: 863: 864: 865: 866:
867: static function new_image($image) {
868: global $_zp_exifvars;
869: $source = '';
870: $metadata_path = '';
871: $files = safe_glob(substr($image->localpath, 0, strrpos($image->localpath, '.')) . '.*');
872: if (count($files) > 0) {
873: foreach ($files as $file) {
874: if (strtolower(getSuffix($file)) == XMP_EXTENSION) {
875: $metadata_path = $file;
876: break;
877: }
878: }
879: }
880: if (!empty($metadata_path)) {
881: $source = self::extractXMP(file_get_contents($metadata_path));
882: } else if (getOption('xmpMetadata_examine_images_' . strtolower(substr(strrchr($image->localpath, "."), 1)))) {
883: $f = file_get_contents($image->localpath);
884: $l = filesize($image->localpath);
885: $abort = 0;
886: $i = 0;
887: while ($i < $l && $abort < 200 && !$source) {
888: $tag = bin2hex(substr($f, $i, 2));
889: $size = hexdec(bin2hex(substr($f, $i + 2, 2)));
890: switch ($tag) {
891: case 'ffe1':
892: case 'ffe2':
893: case 'fffe':
894: case 'ffe0':
895: $source = self::extractXMP($f);
896: $i = $i + $size + 2;
897: $abort = 0;
898: break;
899: default:
900: if ($f{$i} == '<') {
901: $source = self::extractXMP($f);
902: }
903: $i = $i + 1;
904: $abort++;
905: break;
906: }
907: }
908: }
909: if (!empty($source)) {
910: $metadata = self::extract($source);
911: $image->set('hasMetadata', count($metadata > 0));
912: foreach ($metadata as $field => $element) {
913: if (array_key_exists($field, $_zp_exifvars)) {
914: if (!$_zp_exifvars[$field][5]) {
915: continue;
916: }
917: }
918: $v = self::to_string($element);
919:
920: switch ($field) {
921: case 'EXIFDateTimeOriginal':
922: $image->setDateTime($element);
923: break;
924: case 'IPTCImageCaption':
925: $image->setDesc($v);
926: break;
927: case 'IPTCCity':
928: $image->setCity($v);
929: break;
930: case 'IPTCState':
931: $image->setState($v);
932: break;
933: case 'IPTCLocationName':
934: $image->setCountry($v);
935: break;
936: case 'IPTCSubLocation':
937: $image->setLocation($v);
938: break;
939: case 'EXIFExposureTime':
940: $v = formatExposure(self::rationalNum($element));
941: break;
942: case 'EXIFFocalLength':
943: $v = self::rationalNum($element) . ' mm';
944: break;
945: case 'EXIFAperatureValue':
946: case 'EXIFFNumber':
947: $v = 'f/' . self::rationalNum($element);
948: break;
949: case 'EXIFExposureBiasValue':
950: case 'EXIFGPSAltitude':
951: $v = self::rationalNum($element);
952: break;
953: case 'EXIFGPSLatitude':
954: case 'EXIFGPSLongitude':
955: $ref = substr($element, -1, 1);
956: $image->set($field . 'Ref', $ref);
957: $element = substr($element, 0, -1);
958: $n = explode(',', $element);
959: if (count($n) == 3) {
960: $v = $n[0] + ($n[1] + ($n[2] / 60) / 60);
961: } else {
962: $v = $n[0] + $n[1] / 60;
963: }
964: break;
965: case 'rating':
966: $v = min(getoption('rating_stars_count'), $v) * min(1, getOption('rating_split_stars'));
967: $image->set('total_value', $v);
968: $image->set('total_votes', 1);
969: break;
970: case 'watermark':
971: case 'watermark_use':
972: case 'custom_data':
973: case 'codeblock':
974: case 'owner':
975: $image->set($field, $v);
976: break;
977: case 'IPTCKeywords':
978: if (!is_array($element)) {
979: $element = explode(',', $element);
980: }
981: $image->setTags($element);
982: break;
983: }
984: if (array_key_exists($field, $_zp_exifvars)) {
985: $image->set($field, $v);
986: }
987: }
988: $image->save();
989: }
990: return $image;
991: }
992:
993: static function sidecars($types) {
994: $types[] = XMP_EXTENSION;
995: return $types;
996: }
997:
998: static function putXMP($object, $prefix) {
999: if (isset($_POST['xmpMedadataPut_' . $prefix])) {
1000: self::publish($object);
1001: }
1002: return $object;
1003: }
1004:
1005: static function publish($object) {
1006: $desiredtags = array('copyright' => '<dc:rights>',
1007: 'desc' => '<dc:description>',
1008: 'title' => '<dc:title>',
1009: 'tags' => '<dc:subject>',
1010: 'location' => '<Iptc4xmpCore:Location>',
1011: 'city' => '<photoshop:City>',
1012: 'state' => '<photoshop:State>',
1013: 'country' => '<photoshop:Country>',
1014: 'title' => '<photoshop:Headline>',
1015: 'credit' => '<photoshop:Credit>',
1016: 'thumb' => '<zp:Thumbnail>',
1017: 'owner' => '<zp:Owner>',
1018: 'watermark' => '<zp:Watermark>',
1019: 'watermark_use' => '<zp:Watermark_use>',
1020: 'watermark_thumb' => '<zp:Watermark_thumb>',
1021: 'custom_data' => '<zp:CustomData>',
1022: 'codeblock' => '<zp:Codeblock>',
1023: 'date' => '<exif:DateTimeOriginal>',
1024: 'rating' => '<MicrosoftPhoto:Rating>'
1025: );
1026: $process = array('dc', 'Iptc4xmpCore', 'photoshop', 'xap');
1027: if (isAlbumClass($object)) {
1028: $file = rtrim($object->localpath, '/');
1029: $file .= '.xmp';
1030: } else {
1031: $file = stripSuffix($object->localpath) . '.xmp';
1032: }
1033: @chmod($file, 0777);
1034: $f = fopen($file, 'w');
1035: fwrite($f, '<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 4.2-c020 1.124078, Tue Sep 11 2007 23:21:40 ">' . "\n");
1036: fwrite($f, ' <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">' . "\n");
1037: $last_element = $special = $output = false;
1038: foreach ($desiredtags as $field => $elementXML) {
1039: $elementXML = substr($elementXML, 1, -1);
1040: if ($last_element != $elementXML) {
1041: if ($output) {
1042: fwrite($f, ' </rdf:Description>' . "\n");
1043: fwrite($f, ' <rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">' . "\n");
1044: }
1045: $last_element = $elementXML;
1046: $output = false;
1047: }
1048: $v = self::encode($object->get($field));
1049: $tag = $elementXML;
1050: switch ($elementXML) {
1051: case 'dc:creator':
1052: $special = 'rdf:Seq';
1053: $tag = 'rdf:li';
1054: if ($v) {
1055: fwrite($f, " <$elementXML>\n");
1056: fwrite($f, " <$special>\n");
1057: fwrite($f, " <$tag>$v</$tag>\n");
1058: fwrite($f, " </$special>\n");
1059: fwrite($f, " </$elementXML>\n");
1060: $output = true;
1061: }
1062: break;
1063: case 'dc:rights':
1064: case 'xapRights:UsageTerms':
1065: $special = 'rdf:Alt';
1066: $tag = 'rdf:li';
1067: if ($v) {
1068: fwrite($f, " <$elementXML>\n");
1069: fwrite($f, " <$special>\n");
1070: fwrite($f, " <$tag>$v</$tag>\n");
1071: fwrite($f, " </$special>\n");
1072: fwrite($f, " </$elementXML>\n");
1073: $output = true;
1074: }
1075: break;
1076: case 'dc:subject':
1077: $tags = $object->getTags();
1078: if (!empty($tags)) {
1079: fwrite($f, " <$elementXML>\n");
1080: fwrite($f, " <rdf:Bag>\n");
1081: foreach ($tags as $tag) {
1082: fwrite($f, " <rdf:li>" . self::encode($tag) . "</rdf:li>\n");
1083: }
1084: fwrite($f, " </rdf:Bag>\n");
1085: fwrite($f, " </$elementXML>\n");
1086: $output = true;
1087: }
1088: break;
1089: default:
1090: if ($v) {
1091: fwrite($f, " <$tag>$v</$tag>\n");
1092: $output = true;
1093: }
1094: break;
1095: }
1096: }
1097: fwrite($f, ' </rdf:Description>' . "\n");
1098: fwrite($f, ' </rdf:RDF>' . "\n");
1099: fwrite($f, '</x:xmpmeta>' . "\n");
1100: fclose($f);
1101: clearstatcache();
1102: @chmod($file, FILE_MOD);
1103: return gettext('Metadata exported');
1104: }
1105:
1106: static function create($html, $object, $prefix) {
1107: if ($html)
1108: $html .= '<hr />';
1109: $html .= '<label><input type="checkbox" name="xmpMedadataPut_' . $prefix . '" value="1" /> ' . gettext('Export metadata info to XMP sidecar.') . '</label>';
1110: return $html;
1111: }
1112:
1113: static function bulkActions($actions) {
1114: return array_merge($actions, array(gettext('Export Metadata') => 'xmpMetadataPublish'));
1115: }
1116:
1117: }
1118:
1119: ?>