From 0864f58a12ae0e4f8ee15fd6e6acc8a2a47bc54f Mon Sep 17 00:00:00 2001 From: Jaisen Mathai Date: Sun, 22 Jan 2017 00:19:44 -0800 Subject: [PATCH] Add original file name methods to text and media class #186 (#187) --- elodie/compatability.py | 16 +++-- elodie/filesystem.py | 4 +- elodie/media/base.py | 18 ++++- elodie/media/media.py | 44 +++++++++++- elodie/media/text.py | 38 ++++++++-- elodie/tests/files/with-original-name.jpg | Bin 0 -> 18220 bytes elodie/tests/files/with-original-name.txt | 3 + elodie/tests/helper.py | 15 ++-- elodie/tests/media/base_test.py | 35 +++++++-- elodie/tests/media/media_test.py | 82 ++++++++++++++++++++++ elodie/tests/media/text_test.py | 29 ++++++++ 11 files changed, 259 insertions(+), 25 deletions(-) create mode 100644 elodie/tests/files/with-original-name.jpg create mode 100644 elodie/tests/files/with-original-name.txt 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 0000000000000000000000000000000000000000..f6d8064e0b76a18cda1fcedf1fc45e7966f5daed GIT binary patch literal 18220 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