@@ -71,14 +71,18 @@ class Native extends AdapterAbstract
7171 * @var array
7272 */
7373 protected $ iptcMapping = array (
74- 'title ' => '2#005 ' ,
75- 'keywords ' => '2#025 ' ,
76- 'copyright ' => '2#116 ' ,
77- 'caption ' => '2#120 ' ,
78- 'headline ' => '2#105 ' ,
79- 'credit ' => '2#110 ' ,
80- 'source ' => '2#115 ' ,
81- 'jobtitle ' => '2#085 '
74+ 'title ' => '2#005 ' ,
75+ 'keywords ' => '2#025 ' ,
76+ 'copyright ' => '2#116 ' ,
77+ 'caption ' => '2#120 ' ,
78+ 'headline ' => '2#105 ' ,
79+ 'credit ' => '2#110 ' ,
80+ 'source ' => '2#115 ' ,
81+ 'jobtitle ' => '2#085 ' ,
82+ 'city ' => '2#090 ' ,
83+ 'sublocation ' => '2#092 ' ,
84+ 'state ' => '2#095 ' ,
85+ 'country ' => '2#101 '
8286 );
8387
8488
@@ -176,38 +180,27 @@ public function getExifFromFile($file)
176180 {
177181 $ mimeType = mime_content_type ($ file );
178182
179- if (strpos ($ mimeType , 'video ' ) !== 0 ) {
180183
181- // Photo
182- $ sections = $ this ->getRequiredSections ();
183- $ sections = implode (', ' , $ sections );
184- $ sections = (empty ($ sections )) ? null : $ sections ;
185184
186- $ data = @exif_read_data (
187- $ file ,
188- $ sections ,
189- $ this ->getSectionsAsArrays (),
190- $ this ->getIncludeThumbnail ()
191- );
185+ // Photo
186+ $ sections = $ this ->getRequiredSections ();
187+ $ sections = implode (', ' , $ sections );
188+ $ sections = (empty ($ sections )) ? null : $ sections ;
192189
193- if (false === $ data ) {
194- return false ;
195- }
190+ $ data = @exif_read_data (
191+ $ file ,
192+ $ sections ,
193+ $ this ->getSectionsAsArrays (),
194+ $ this ->getIncludeThumbnail ()
195+ );
196196
197- $ xmpData = $ this ->getIptcData ($ file );
198- $ data = array_merge ($ data , array (self ::SECTION_IPTC => $ xmpData ));
199-
200- } else {
201- // Video
202- try {
197+ if (false === $ data ) {
198+ return false ;
199+ }
203200
204- $ data = $ this ->getVideoData ($ file );
205- $ data[ ' MimeType ' ] = $ mimeType ;
201+ $ xmpData = $ this ->getIptcData ($ file );
202+ $ data = array_merge ( $ data, array ( self :: SECTION_IPTC => $ xmpData )) ;
206203
207- } catch (Exception $ exception ) {
208- Logs::error (__METHOD__ , __LINE__ , $ exception ->getMessage ());
209- }
210- }
211204
212205 // map the data:
213206 $ mapper = $ this ->getMapper ();
@@ -222,163 +215,6 @@ public function getExifFromFile($file)
222215 return $ exif ;
223216 }
224217
225- /**
226- * Returns an array of video data
227- *
228- * @param string $file The file to read the video data from
229- * @return array
230- */
231- public function getVideoData ($ filename )
232- {
233-
234- $ metadata ['FileSize ' ] = filesize ($ filename );
235-
236- $ path_ffmpeg = exec ('which ffmpeg ' );
237- $ path_ffprobe = exec ('which ffprobe ' );
238- $ ffprobe = FFMpeg \FFProbe::create (array (
239- 'ffmpeg.binaries ' => $ path_ffmpeg ,
240- 'ffprobe.binaries ' => $ path_ffprobe ,
241- ));
242-
243- $ stream = $ ffprobe ->streams ($ filename )->videos ()->first ()->all ();
244- $ format = $ ffprobe ->format ($ filename )->all ();
245- if (isset ($ stream ['width ' ])) {
246- $ metadata ['Width ' ] = $ stream ['width ' ];
247- }
248- if (isset ($ stream ['height ' ])) {
249- $ metadata ['Height ' ] = $ stream ['height ' ];
250- }
251- if (isset ($ stream ['tags ' ]) && isset ($ stream ['tags ' ]['rotate ' ]) && ($ stream ['tags ' ]['rotate ' ] === '90 ' || $ stream ['tags ' ]['rotate ' ] === '270 ' )) {
252- $ tmp = $ metadata ['Width ' ];
253- $ metadata ['Width ' ] = $ metadata ['Height ' ];
254- $ metadata ['Height ' ] = $ tmp ;
255- }
256- if (isset ($ stream ['avg_frame_rate ' ])) {
257- $ framerate = explode ('/ ' , $ stream ['avg_frame_rate ' ]);
258- if (count ($ framerate ) == 1 ) {
259- $ framerate = $ framerate [0 ];
260- } elseif (count ($ framerate ) == 2 && $ framerate [1 ] != 0 ) {
261- $ framerate = number_format ($ framerate [0 ] / $ framerate [1 ], 3 );
262- } else {
263- $ framerate = '' ;
264- }
265- if ($ framerate !== '' ) {
266- $ metadata ['framerate ' ] = $ framerate ;
267- }
268- }
269- if (isset ($ format ['duration ' ])) {
270- $ metadata ['duration ' ] = number_format ($ format ['duration ' ], 3 );
271- }
272- if (isset ($ format ['tags ' ])) {
273- if (isset ($ format ['tags ' ]['creation_time ' ]) && strtotime ($ format ['tags ' ]['creation_time ' ]) !== 0 ) {
274- $ metadata ['DateTimeOriginal ' ] = date ('Y-m-d H:i:s ' , strtotime ($ format ['tags ' ]['creation_time ' ]));
275- }
276- if (isset ($ format ['tags ' ]['location ' ])) {
277- $ matches = [];
278- preg_match ('/^([+-][0-9\.]+)([+-][0-9\.]+)\/$/ ' , $ format ['tags ' ]['location ' ], $ matches );
279- if (count ($ matches ) == 3 &&
280- !preg_match ('/^\+0+\.0+$/ ' , $ matches [1 ]) &&
281- !preg_match ('/^\+0+\.0+$/ ' , $ matches [2 ])) {
282- $ metadata ['GPSLatitude ' ] = $ matches [1 ];
283- $ metadata ['GPSLongitude ' ] = $ matches [2 ];
284- }
285- }
286- // QuickTime File Format defines several additional metadata
287- // Source: https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/Metadata/Metadata.html
288- // Special case: iPhones write into tags->creation_time the creation time of the file
289- // -> When converting the video from HEVC (iOS Video format) to MOV, the creation_time
290- // is the time when the mov file was created, not when the video was shot (fixed in iOS12)
291- // (see e.g. https://michaelkummer.com/tech/apple/photos-videos-wrong-date/ (for the symptom)
292- // Solution: Use com.apple.quicktime.creationdate which is the true creation date of the video
293- if (isset ($ format ['tags ' ]['com.apple.quicktime.creationdate ' ])) {
294- $ metadata ['DateTimeOriginal ' ] = date ('Y-m-d H:i:s ' , strtotime ($ format ['tags ' ]['com.apple.quicktime.creationdate ' ]));
295- }
296- if (isset ($ format ['tags ' ]['com.apple.quicktime.description ' ])) {
297- $ metadata ['description ' ] = $ format ['tags ' ]['com.apple.quicktime.description ' ];
298- }
299- if (isset ($ format ['tags ' ]['com.apple.quicktime.title ' ])) {
300- $ metadata ['title ' ] = $ format ['tags ' ]['com.apple.quicktime.title ' ];
301- }
302- if (isset ($ format ['tags ' ]['com.apple.quicktime.keywords ' ])) {
303- $ metadata ['keywords ' ] = $ format ['tags ' ]['com.apple.quicktime.keywords ' ];
304- }
305- if (isset ($ format ['tags ' ]['com.apple.quicktime.location.ISO6709 ' ])) {
306- $ location_data = $ this ->readISO6709 ($ format ['tags ' ]['com.apple.quicktime.location.ISO6709 ' ]);
307- $ metadata ['GPSLatitude ' ] = $ location_data ['latitude ' ];
308- $ metadata ['GPSLongitude ' ] = $ location_data ['longitude ' ];
309- $ metadata ['GPSAltitude ' ] = $ location_data ['altitude ' ];
310- }
311- // Not documented, but available on iPhone videos
312- if (isset ($ format ['tags ' ]['com.apple.quicktime.make ' ])) {
313- $ metadata ['Make ' ] = $ format ['tags ' ]['com.apple.quicktime.make ' ];
314- }
315- // Not documented, but available on iPhone videos
316- if (isset ($ format ['tags ' ]['com.apple.quicktime.model ' ])) {
317- $ metadata ['Model ' ] = $ format ['tags ' ]['com.apple.quicktime.model ' ];
318- }
319- }
320-
321- return $ metadata ;
322- }
323-
324- /**
325- * Converts results of ISO6709 parsing
326- * to decimal format for latitude and longitude
327- * See https://github.com/seanson/python-iso6709.git.
328- *
329- * @param string sign
330- * @param string degrees
331- * @param string minutes
332- * @param string seconds
333- * @param string fraction
334- *
335- * @return float
336- */
337- private function convertDMStoDecimal (string $ sign , string $ degrees , string $ minutes , string $ seconds , string $ fraction ): float
338- {
339- if ($ fraction !== '' ) {
340- if ($ seconds !== '' ) {
341- $ seconds = $ seconds . $ fraction ;
342- } elseif ($ minutes !== '' ) {
343- $ minutes = $ minutes . $ fraction ;
344- } else {
345- $ degrees = $ degrees . $ fraction ;
346- }
347- }
348- $ decimal = floatval ($ degrees ) + floatval ($ minutes ) / 60.0 + floatval ($ seconds ) / 3600.0 ;
349- if ($ sign == '- ' ) {
350- $ decimal = -1.0 * $ decimal ;
351- }
352- return $ decimal ;
353- }
354-
355- /**
356- * Returns the latitude, longitude and altitude
357- * of a GPS coordiante formattet with ISO6709
358- * See https://github.com/seanson/python-iso6709.git.
359- *
360- * @param string val_ISO6709
361- *
362- * @return array
363- */
364- private function readISO6709 (string $ val_ISO6709 ): array
365- {
366- $ return = [
367- 'latitude ' => null ,
368- 'longitude ' => null ,
369- 'altitude ' => null ,
370- ];
371- $ matches = [];
372- // Adjustment compared to https://github.com/seanson/python-iso6709.git
373- // Altitude have format +XX.XXXX -> Adjustment for decimal
374- preg_match ('/^(?<lat_sign>\+|-)(?<lat_degrees>[0,1]?\d{2})(?<lat_minutes>\d{2}?)?(?<lat_seconds>\d{2}?)?(?<lat_fraction>\.\d+)?(?<lng_sign>\+|-)(?<lng_degrees>[0,1]?\d{2})(?<lng_minutes>\d{2}?)?(?<lng_seconds>\d{2}?)?(?<lng_fraction>\.\d+)?(?<alt>[\+\-][0-9]\d*(\.\d+)?)?\/$/ ' , $ val_ISO6709 , $ matches );
375- $ return ['latitude ' ] = $ this ->convertDMStoDecimal ($ matches ['lat_sign ' ], $ matches ['lat_degrees ' ], $ matches ['lat_minutes ' ], $ matches ['lat_seconds ' ], $ matches ['lat_fraction ' ]);
376- $ return ['longitude ' ] = $ this ->convertDMStoDecimal ($ matches ['lng_sign ' ], $ matches ['lng_degrees ' ], $ matches ['lng_minutes ' ], $ matches ['lng_seconds ' ], $ matches ['lng_fraction ' ]);
377- if (isset ($ matches ['alt ' ])) {
378- $ return ['altitude ' ] = doubleval ($ matches ['alt ' ]);
379- }
380- return $ return ;
381- }
382218
383219 /**
384220 * Returns an array of IPTC data
0 commit comments