gh-28 Add no-exif file for tests and add test cases
| @ -23,8 +23,6 @@ Media class for general video operations | |||||||
| class Media(object): | class Media(object): | ||||||
|     # class / static variable accessible through get_valid_extensions() |     # class / static variable accessible through get_valid_extensions() | ||||||
|     __name__ = 'Media' |     __name__ = 'Media' | ||||||
|     video_extensions = ('avi','m4v','mov','mp4','3gp') |  | ||||||
|     photo_extensions = ('jpg', 'jpeg', 'nef', 'dng', 'gif') |  | ||||||
| 
 | 
 | ||||||
|     """ |     """ | ||||||
|     @param, source, string, The fully qualified path to the video file |     @param, source, string, The fully qualified path to the video file | ||||||
| @ -88,41 +86,6 @@ class Media(object): | |||||||
|     def is_valid(self): |     def is_valid(self): | ||||||
|         return False |         return False | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     """ |  | ||||||
|     Get the date which the photo was taken. |  | ||||||
|     The date value returned is defined by the min() of mtime and ctime. |  | ||||||
| 
 |  | ||||||
|     @returns, time object or None for non-photo files or 0 timestamp |  | ||||||
|     """ |  | ||||||
|     def get_date_taken(self): |  | ||||||
|         if(not self.is_valid()): |  | ||||||
|             return None |  | ||||||
| 
 |  | ||||||
|         source = self.source |  | ||||||
|         seconds_since_epoch = min(os.path.getmtime(source), os.path.getctime(source)) |  | ||||||
|         # We need to parse a string from EXIF into a timestamp. |  | ||||||
|         # EXIF DateTimeOriginal and EXIF DateTime are both stored in %Y:%m:%d %H:%M:%S format |  | ||||||
|         # we use date.strptime -> .timetuple -> time.mktime to do the conversion in the local timezone |  | ||||||
|         # EXIF DateTime is already stored as a timestamp |  | ||||||
|         # Sourced from https://github.com/photo/frontend/blob/master/src/libraries/models/Photo.php#L500 |  | ||||||
|         exif = self.get_exif() |  | ||||||
|         for key in self.exif_map['date_taken']: |  | ||||||
|             try: |  | ||||||
|                 if(key in exif): |  | ||||||
|                     if(re.match('\d{4}(-|:)\d{2}(-|:)\d{2}', str(exif[key].value)) is not None): |  | ||||||
|                         seconds_since_epoch = time.mktime(exif[key].value.timetuple()) |  | ||||||
|                         break; |  | ||||||
|             except BaseException as e: |  | ||||||
|                 if(constants.debug == True): |  | ||||||
|                     print e |  | ||||||
|                 pass |  | ||||||
| 
 |  | ||||||
|         if(seconds_since_epoch == 0): |  | ||||||
|             return None |  | ||||||
| 
 |  | ||||||
|         return time.gmtime(seconds_since_epoch) |  | ||||||
| 
 |  | ||||||
|     """ |     """ | ||||||
|     Read EXIF from a photo file. |     Read EXIF from a photo file. | ||||||
|     We store the result in a member variable so we can call get_exif() often without performance degredation |     We store the result in a member variable so we can call get_exif() often without performance degredation | ||||||
|  | |||||||
| @ -86,7 +86,41 @@ class Photo(Media): | |||||||
|             return None |             return None | ||||||
| 
 | 
 | ||||||
|     """ |     """ | ||||||
|     Check the file extension against valid file extensions as returned by get_valid_extensions() |     Get the date which the photo was taken. | ||||||
|  |     The date value returned is defined by the min() of mtime and ctime. | ||||||
|  | 
 | ||||||
|  |     @returns, time object or None for non-photo files or 0 timestamp | ||||||
|  |     """ | ||||||
|  |     def get_date_taken(self): | ||||||
|  |         if(not self.is_valid()): | ||||||
|  |             return None | ||||||
|  | 
 | ||||||
|  |         source = self.source | ||||||
|  |         seconds_since_epoch = min(os.path.getmtime(source), os.path.getctime(source)) | ||||||
|  |         # We need to parse a string from EXIF into a timestamp. | ||||||
|  |         # EXIF DateTimeOriginal and EXIF DateTime are both stored in %Y:%m:%d %H:%M:%S format | ||||||
|  |         # we use date.strptime -> .timetuple -> time.mktime to do the conversion in the local timezone | ||||||
|  |         # EXIF DateTime is already stored as a timestamp | ||||||
|  |         # Sourced from https://github.com/photo/frontend/blob/master/src/libraries/models/Photo.php#L500 | ||||||
|  |         exif = self.get_exif() | ||||||
|  |         for key in self.exif_map['date_taken']: | ||||||
|  |             try: | ||||||
|  |                 if(key in exif): | ||||||
|  |                     if(re.match('\d{4}(-|:)\d{2}(-|:)\d{2}', str(exif[key].value)) is not None): | ||||||
|  |                         seconds_since_epoch = time.mktime(exif[key].value.timetuple()) | ||||||
|  |                         break; | ||||||
|  |             except BaseException as e: | ||||||
|  |                 if(constants.debug == True): | ||||||
|  |                     print e | ||||||
|  |                 pass | ||||||
|  | 
 | ||||||
|  |         if(seconds_since_epoch == 0): | ||||||
|  |             return None | ||||||
|  | 
 | ||||||
|  |         return time.gmtime(seconds_since_epoch) | ||||||
|  | 
 | ||||||
|  |     """ | ||||||
|  |     Check the file extension against valid file extensions as returned by self.extensions | ||||||
|      |      | ||||||
|     @returns, boolean |     @returns, boolean | ||||||
|     """ |     """ | ||||||
| @ -98,7 +132,6 @@ class Photo(Media): | |||||||
|         if(imghdr.what(source) is None): |         if(imghdr.what(source) is None): | ||||||
|             return False; |             return False; | ||||||
| 
 | 
 | ||||||
|         # we can't use self.__get_extension else we'll endlessly recurse |  | ||||||
|         return os.path.splitext(source)[1][1:].lower() in self.extensions |         return os.path.splitext(source)[1][1:].lower() in self.extensions | ||||||
| 
 | 
 | ||||||
|     """ |     """ | ||||||
| @ -174,4 +207,4 @@ class Photo(Media): | |||||||
|     """ |     """ | ||||||
|     @classmethod |     @classmethod | ||||||
|     def get_valid_extensions(Photo): |     def get_valid_extensions(Photo): | ||||||
|         return Media.photo_extensions |         return Photo.extensions | ||||||
|  | |||||||
							
								
								
									
										
											BIN
										
									
								
								elodie/tests/files/no-exif.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 222 B | 
| Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 13 KiB | 
| Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 14 KiB | 
| Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 14 KiB | 
| Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 14 KiB | 
| Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 14 KiB | 
| Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 14 KiB | 
| Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 13 KiB | 
| @ -3,14 +3,10 @@ | |||||||
| import os | import os | ||||||
| import sys | import sys | ||||||
| 
 | 
 | ||||||
| import hashlib | import datetime | ||||||
| import random |  | ||||||
| import re |  | ||||||
| import shutil | import shutil | ||||||
| import string |  | ||||||
| import tempfile | import tempfile | ||||||
| import time | import time | ||||||
| import datetime |  | ||||||
| 
 | 
 | ||||||
| from nose.plugins.skip import SkipTest | from nose.plugins.skip import SkipTest | ||||||
| 
 | 
 | ||||||
| @ -25,7 +21,7 @@ os.environ['TZ'] = 'GMT' | |||||||
| 
 | 
 | ||||||
| def test_photo_extensions(): | def test_photo_extensions(): | ||||||
|     photo = Photo() |     photo = Photo() | ||||||
|     extensions = photo.photo_extensions |     extensions = photo.extensions | ||||||
| 
 | 
 | ||||||
|     assert 'jpg' in extensions |     assert 'jpg' in extensions | ||||||
|     assert 'jpeg' in extensions |     assert 'jpeg' in extensions | ||||||
| @ -75,6 +71,29 @@ def test_get_coordinate_longitude(): | |||||||
| 
 | 
 | ||||||
|     assert coordinate == -122.033383611, coordinate |     assert coordinate == -122.033383611, coordinate | ||||||
| 
 | 
 | ||||||
|  | def test_get_coordinates_without_exif(): | ||||||
|  |     photo = Photo(helper.get_file('no-exif.jpg')) | ||||||
|  |     latitude = photo.get_coordinate('latitude') | ||||||
|  |     longitude = photo.get_coordinate('longitude') | ||||||
|  | 
 | ||||||
|  |     assert latitude is None, latitude | ||||||
|  |     assert longitude is None, longitude | ||||||
|  | 
 | ||||||
|  | def test_get_date_taken(): | ||||||
|  |     photo = Photo(helper.get_file('plain.jpg')) | ||||||
|  |     date_taken = photo.get_date_taken() | ||||||
|  | 
 | ||||||
|  |     assert date_taken == (2015, 12, 5, 0, 59, 26, 5, 339, 0), date_taken | ||||||
|  | 
 | ||||||
|  | def test_get_date_taken_without_exif(): | ||||||
|  |     source = helper.get_file('no-exif.jpg') | ||||||
|  |     photo = Photo(source) | ||||||
|  |     date_taken = photo.get_date_taken() | ||||||
|  | 
 | ||||||
|  |     date_taken_from_file = time.gmtime(min(os.path.getmtime(source), os.path.getctime(source))) | ||||||
|  | 
 | ||||||
|  |     assert date_taken == date_taken_from_file, date_taken | ||||||
|  | 
 | ||||||
| def test_is_valid(): | def test_is_valid(): | ||||||
|     photo = Photo(helper.get_file('with-location.jpg')) |     photo = Photo(helper.get_file('with-location.jpg')) | ||||||
| 
 | 
 | ||||||
| @ -141,10 +160,6 @@ def test_set_title(): | |||||||
|     photo = Photo(origin) |     photo = Photo(origin) | ||||||
|     origin_metadata = photo.get_metadata() |     origin_metadata = photo.get_metadata() | ||||||
| 
 | 
 | ||||||
|     # Verify that original photo has no location information |  | ||||||
|     assert origin_metadata['latitude'] is None, origin_metadata['latitude'] |  | ||||||
|     assert origin_metadata['longitude'] is None, origin_metadata['longitude'] |  | ||||||
| 
 |  | ||||||
|     status = photo.set_title('my photo title') |     status = photo.set_title('my photo title') | ||||||
| 
 | 
 | ||||||
|     assert status == True, status |     assert status == True, status | ||||||
| @ -166,10 +181,6 @@ def test_set_title_non_ascii(): | |||||||
|     photo = Photo(origin) |     photo = Photo(origin) | ||||||
|     origin_metadata = photo.get_metadata() |     origin_metadata = photo.get_metadata() | ||||||
| 
 | 
 | ||||||
|     # Verify that original photo has no location information |  | ||||||
|     assert origin_metadata['latitude'] is None, origin_metadata['latitude'] |  | ||||||
|     assert origin_metadata['longitude'] is None, origin_metadata['longitude'] |  | ||||||
| 
 |  | ||||||
|     status = photo.set_title('形声字 / 形聲字') |     status = photo.set_title('形声字 / 形聲字') | ||||||
| 
 | 
 | ||||||
|     assert status == True, status |     assert status == True, status | ||||||
|  | |||||||
 Jaisen Mathai
						Jaisen Mathai