diff --git a/elodie/compatability.py b/elodie/compatability.py index ac00f17..e11967d 100644 --- a/elodie/compatability.py +++ b/elodie/compatability.py @@ -3,6 +3,7 @@ import shutil from elodie import constants + def _decode(string, encoding='utf8'): """Return a utf8 encoded unicode string. @@ -22,6 +23,7 @@ def _decode(string, encoding='utf8'): return string + def _copyfile(src, dst): # Python 3 hangs using open/write method if (constants.python_version == 3): @@ -37,7 +39,7 @@ def _copyfile(src, dst): WRITE_FLAGS = os.O_WRONLY | os.O_CREAT | os.O_TRUNC | O_BINARY TEN_MEGABYTES = 10485760 BUFFER_SIZE = min(TEN_MEGABYTES, os.path.getsize(src)) - + try: fin = os.open(src, READ_FLAGS) stat = os.fstat(fin) @@ -45,7 +47,11 @@ def _copyfile(src, dst): for x in iter(lambda: os.read(fin, BUFFER_SIZE), ""): os.write(fout, x) finally: - try: os.close(fin) - except: pass - try: os.close(fout) - except: pass + try: + os.close(fin) + except: + pass + try: + os.close(fout) + except: + pass diff --git a/elodie/filesystem.py b/elodie/filesystem.py index 97d1aa1..9577e38 100644 --- a/elodie/filesystem.py +++ b/elodie/filesystem.py @@ -291,6 +291,7 @@ class FileSystem(object): print('%s is not a valid media file. Skipping...' % _file) return + media.set_original_name() metadata = media.get_metadata() directory_name = self.get_folder_path(metadata) @@ -334,9 +335,8 @@ class FileSystem(object): # Do not use copy2(), will have an issue when copying to a # network/mounted drive using copy and manual # set_date_from_filename gets the job done - # Do not use copy2(), will have an issue when copying to a network/mounted drive # shutil.copy seems slow, changing to streaming according to - # http://stackoverflow.com/questions/22078621/python-how-to-copy-files-fast + # http://stackoverflow.com/questions/22078621/python-how-to-copy-files-fast # noqa compatability._copyfile(_file, dest_path) self.set_utime(media) diff --git a/elodie/media/base.py b/elodie/media/base.py index d0dee94..da9d466 100644 --- a/elodie/media/base.py +++ b/elodie/media/base.py @@ -90,6 +90,7 @@ class Base(object): 'album': self.get_album(), 'title': self.get_title(), 'mime_type': self.get_mimetype(), + 'original_name': self.get_original_name(), 'base_name': os.path.splitext(os.path.basename(source))[0], 'extension': self.get_extension(), 'directory_path': os.path.dirname(source) @@ -100,7 +101,7 @@ class Base(object): def get_mimetype(self): """Get the mimetype of the file. - :returns: str or None for a non-video + :returns: str or None for unsupported files. """ if(not self.is_valid()): return None @@ -112,6 +113,15 @@ class Base(object): return mimetype[0] + def get_original_name(self): + """Get the original name of the file from before it was imported. + Does not include the extension. + Overridden by Media class for files with EXIF. + + :returns: str or None for unsupported files. + """ + return None + def get_title(self): """Base method for getting the title of a file @@ -185,6 +195,12 @@ class Base(object): if(key in metadata): self.metadata[key] = kwargs[key] + def set_original_name(self): + """Stores the original file name into EXIF/metadata. + :returns: bool + """ + return False + @classmethod def get_class_by_file(cls, _file, classes): """Static method to get a media object by file. diff --git a/elodie/media/media.py b/elodie/media/media.py index f721193..40c406c 100644 --- a/elodie/media/media.py +++ b/elodie/media/media.py @@ -10,6 +10,8 @@ are used to represent the actual files. """ from __future__ import print_function +import os + # load modules from elodie import constants from elodie.dependencies import get_exiftool @@ -46,6 +48,7 @@ class Media(Base): self.longitude_keys = ['EXIF:GPSLongitude'] self.latitude_ref_key = 'EXIF:GPSLatitudeRef' self.longitude_ref_key = 'EXIF:GPSLongitudeRef' + self.original_name_key = 'XMP:OriginalFileName' self.set_gps_ref = True self.exiftool_addedargs = [ '-overwrite_original', @@ -91,10 +94,10 @@ class Media(Base): if key not in exif: continue - # Cast coordinate to a float due to a bug in exiftool's + # Cast coordinate to a float due to a bug in exiftool's # -json output format. # https://github.com/jmathai/elodie/issues/171 - # http://u88.n24.queensu.ca/exiftool/forum/index.php/topic,7952.0.html #noqa + # http://u88.n24.queensu.ca/exiftool/forum/index.php/topic,7952.0.html # noqa this_coordinate = float(exif[key]) # TODO: verify that we need to check ref key @@ -129,6 +132,24 @@ class Media(Base): return metadata + def get_original_name(self): + """Get the original name stored in EXIF. + + :returns: str + """ + if(not self.is_valid()): + return None + + exiftool_attributes = self.get_exiftool_attributes() + + if exiftool_attributes is None: + return None + + if(self.original_name_key not in exiftool_attributes): + return None + + return exiftool_attributes[self.original_name_key] + def get_title(self): """Get the title for a photo of video @@ -213,6 +234,25 @@ class Media(Base): return status + def set_original_name(self): + """Sets the original name EXIF tag if not already set. + + :returns: True, False, None + """ + if(not self.is_valid()): + return None + + # If EXIF original name tag is set then we return. + if self.get_original_name() is not None: + return None + + source = self.source + name = os.path.basename(source) + tags = {self.original_name_key: name} + status = self.__set_tags(tags) + self.reset_cache() + return status + def set_title(self, title): """Set title for a photo. diff --git a/elodie/media/text.py b/elodie/media/text.py index c7e54c7..3f4ea5b 100644 --- a/elodie/media/text.py +++ b/elodie/media/text.py @@ -72,6 +72,16 @@ class Text(Base): self.parse_metadata_line() return super(Text, self).get_metadata() + def get_original_name(self): + self.parse_metadata_line() + + # We return the value if found in metadata + if(isinstance(self.metadata_line, dict) and + 'original_name' in self.metadata_line): + return self.metadata_line['original_name'] + + return super(Text, self).get_original_name() + def get_title(self): self.parse_metadata_line() @@ -92,11 +102,6 @@ class Text(Base): self.reset_cache() return status - def set_location(self, latitude, longitude): - status = self.write_metadata(latitude=latitude, longitude=longitude) - self.reset_cache() - return status - def set_date_taken(self, passed_in_time): if(time is None): return False @@ -106,6 +111,29 @@ class Text(Base): self.reset_cache() return status + def set_original_name(self): + """Sets the original name if not already set. + + :returns: True, False, None + """ + if(not self.is_valid()): + return None + + # If EXIF original name tag is set then we return. + if self.get_original_name() is not None: + return None + + source = self.source + name = os.path.basename(source) + status = self.write_metadata(original_name=name) + self.reset_cache() + return status + + def set_location(self, latitude, longitude): + status = self.write_metadata(latitude=latitude, longitude=longitude) + self.reset_cache() + return status + def parse_metadata_line(self): if isinstance(self.metadata_line, dict): return self.metadata_line diff --git a/elodie/tests/files/with-original-name.jpg b/elodie/tests/files/with-original-name.jpg new file mode 100644 index 0000000..f6d8064 Binary files /dev/null and b/elodie/tests/files/with-original-name.jpg differ diff --git a/elodie/tests/files/with-original-name.txt b/elodie/tests/files/with-original-name.txt new file mode 100644 index 0000000..c35b56a --- /dev/null +++ b/elodie/tests/files/with-original-name.txt @@ -0,0 +1,3 @@ +{"date_taken":1460027726.0,"latitude":"51.521435","longitude":"0.162714","title":"sample title","original_name":"originalname.txt"} + +This file has a valid header. diff --git a/elodie/tests/helper.py b/elodie/tests/helper.py index a73fd45..07673fb 100644 --- a/elodie/tests/helper.py +++ b/elodie/tests/helper.py @@ -36,11 +36,16 @@ def create_working_folder(): def download_file(name, destination): try: - final_name = '{}/{}{}'.format(destination, random_string(10), os.path.splitext(name)[1]) - urllib.urlretrieve( - 'https://s3.amazonaws.com/jmathai/github/elodie/{}'.format(name), - final_name - ) + url_to_file = 'https://s3.amazonaws.com/jmathai/github/elodie/{}'.format(name) + # urlretrieve works differently for python 2 and 3 + if constants.python_version < 3: + final_name = '{}/{}{}'.format(destination, random_string(10), os.path.splitext(name)[1]) + urllib.urlretrieve( + url_to_file, + final_name + ) + else: + final_name, headers = urllib.request.urlretrieve(url_to_file) return final_name except Exception as e: return False diff --git a/elodie/tests/media/base_test.py b/elodie/tests/media/base_test.py index f9abe98..bc9dfbf 100644 --- a/elodie/tests/media/base_test.py +++ b/elodie/tests/media/base_test.py @@ -24,6 +24,10 @@ from elodie.media.video import Video os.environ['TZ'] = 'GMT' +def test_get_all_subclasses(): + subclasses = get_all_subclasses(Base) + expected = {Media, Base, Text, Photo, Video, Audio} + assert subclasses == expected, subclasses def test_get_class_by_file_without_extension(): base_file = helper.get_file('withoutextension') @@ -32,6 +36,32 @@ def test_get_class_by_file_without_extension(): assert cls is None, cls +def test_get_original_name(): + temporary_folder, folder = helper.create_working_folder() + + origin = '%s/%s' % (folder, 'with-original-name.jpg') + file = helper.get_file('with-original-name.jpg') + + shutil.copyfile(file, origin) + + media = Media.get_class_by_file(origin, [Photo]) + original_name = media.get_original_name() + + assert original_name == 'originalfilename.jpg', original_name + +def test_get_original_name_invalid_file(): + temporary_folder, folder = helper.create_working_folder() + + origin = '%s/%s' % (folder, 'invalid.jpg') + file = helper.get_file('invalid.jpg') + + shutil.copyfile(file, origin) + + media = Media.get_class_by_file(origin, [Photo]) + original_name = media.get_original_name() + + assert original_name is None, original_name + def test_set_album_from_folder_invalid_file(): temporary_folder, folder = helper.create_working_folder() @@ -106,8 +136,3 @@ def test_set_metadata_basename(): new_metadata = photo.get_metadata() assert new_metadata['base_name'] == new_basename, new_metadata['base_name'] - -def test_get_all_subclasses(): - subclasses = get_all_subclasses(Base) - expected = {Media, Base, Text, Photo, Video, Audio} - assert subclasses == expected, subclasses diff --git a/elodie/tests/media/media_test.py b/elodie/tests/media/media_test.py index ef7abaf..b4d0743 100644 --- a/elodie/tests/media/media_test.py +++ b/elodie/tests/media/media_test.py @@ -61,6 +61,88 @@ def test_get_class_by_file_invalid_type(): [Photo, Video, Audio]) assert media is None +def test_get_original_name(): + temporary_folder, folder = helper.create_working_folder() + + origin = '%s/%s' % (folder, 'with-original-name.jpg') + file = helper.get_file('with-original-name.jpg') + + shutil.copyfile(file, origin) + + media = Media.get_class_by_file(origin, [Photo]) + original_name = media.get_original_name() + + assert original_name == 'originalfilename.jpg', original_name + +def test_get_original_name_invalid_file(): + temporary_folder, folder = helper.create_working_folder() + + origin = '%s/%s' % (folder, 'invalid.jpg') + file = helper.get_file('invalid.jpg') + + shutil.copyfile(file, origin) + + media = Media.get_class_by_file(origin, [Photo]) + original_name = media.get_original_name() + + assert original_name is None, original_name + +def test_set_original_name_when_exists(): + temporary_folder, folder = helper.create_working_folder() + + origin = '%s/%s' % (folder, 'with-original-name.jpg') + file = helper.get_file('with-original-name.jpg') + + shutil.copyfile(file, origin) + + media = Media.get_class_by_file(origin, [Photo]) + result = media.set_original_name() + + assert result is None, result + +def test_set_original_name_when_does_not_exist(): + temporary_folder, folder = helper.create_working_folder() + + origin = '%s/%s' % (folder, 'plain.jpg') + file = helper.get_file('plain.jpg') + + shutil.copyfile(file, origin) + + media = Media.get_class_by_file(origin, [Photo]) + metadata_before = media.get_metadata() + result = media.set_original_name() + metadata_after = media.get_metadata() + + assert metadata_before['original_name'] is None, metadata_before + assert metadata_after['original_name'] == 'plain.jpg', metadata_after + assert result is True, result + +def test_set_original_name(): + files = ['plain.jpg', 'audio.m4a', 'photo.nef', 'video.mov'] + + for file in files: + ext = os.path.splitext(file)[1] + + temporary_folder, folder = helper.create_working_folder() + + random_file_name = '%s%s' % (helper.random_string(10), ext) + origin = '%s/%s' % (folder, random_file_name) + file_path = helper.get_file(file) + if file_path is False: + file_path = helper.download_file(file, folder) + + shutil.copyfile(file_path, origin) + + media = Media.get_class_by_file(origin, [Audio, Media, Photo, Video]) + metadata = media.get_metadata() + media.set_original_name() + metadata_updated = media.get_metadata() + + shutil.rmtree(folder) + + assert metadata['original_name'] is None, metadata['original_name'] + assert metadata_updated['original_name'] == random_file_name, metadata_updated['original_name'] + def is_valid(): media = Media() diff --git a/elodie/tests/media/text_test.py b/elodie/tests/media/text_test.py index 1e586b6..1721e44 100644 --- a/elodie/tests/media/text_test.py +++ b/elodie/tests/media/text_test.py @@ -29,6 +29,18 @@ def test_text_extensions(): assert extensions == valid_extensions, valid_extensions +def test_get_original_name(): + media = Text(helper.get_file('with-original-name.txt')) + original_name = media.get_original_name() + + assert original_name == 'originalname.txt', original_name + +def test_get_original_name_when_does_not_exist(): + media = Text(helper.get_file('valid.txt')) + original_name = media.get_original_name() + + assert original_name is None, original_name + def test_get_title(): text = Text(helper.get_file('valid.txt')) text.get_metadata() @@ -268,3 +280,20 @@ def test_set_location_without_header(): shutil.rmtree(folder) assert helper.isclose(metadata['latitude'], 11.1111111111), metadata['latitude'] + +def test_set_original_name(): + temporary_folder, folder = helper.create_working_folder() + + random_file_name = '%s.txt' % helper.random_string(10) + origin = '%s/%s' % (folder, random_file_name) + shutil.copyfile(helper.get_file('valid.txt'), origin) + + text = Text(origin) + metadata = text.get_metadata() + text.set_original_name() + metadata_updated = text.get_metadata() + + shutil.rmtree(folder) + + assert metadata['original_name'] is None, metadata['original_name'] + assert metadata_updated['original_name'] == random_file_name, metadata_updated['original_name']