From cb8a4cd24e8250498eaf028c87ff6b7cd77cd854 Mon Sep 17 00:00:00 2001 From: Cedric Leporcq Date: Sun, 20 Jun 2021 19:51:21 +0200 Subject: [PATCH] Refactoring media class --- elodie.py | 22 +- elodie/filesystem.py | 8 +- elodie/media/audio.py | 11 +- elodie/media/base.py | 264 --------------- elodie/media/media.py | 327 ++++++++++++++++--- elodie/media/photo.py | 4 +- elodie/media/video.py | 34 +- elodie/tools/add_original_name.py | 3 +- tests/media/base_test.py | 138 -------- tests/media/{audio_test.py => test_audio.py} | 15 +- tests/media/{media_test.py => test_media.py} | 163 ++++++--- tests/media/{photo_test.py => test_photo.py} | 33 +- tests/media/{video_test.py => test_video.py} | 3 +- 13 files changed, 469 insertions(+), 556 deletions(-) delete mode 100644 elodie/media/base.py delete mode 100644 tests/media/base_test.py rename tests/media/{audio_test.py => test_audio.py} (93%) rename tests/media/{media_test.py => test_media.py} (62%) rename tests/media/{photo_test.py => test_photo.py} (93%) rename tests/media/{video_test.py => test_video.py} (96%) diff --git a/elodie.py b/elodie.py index 3b61777..4229987 100755 --- a/elodie.py +++ b/elodie.py @@ -155,8 +155,8 @@ def _import(destination, source, file, album_from_folder, trash, sys.exit(1) @click.command('generate-db') -@click.option('--source', type=click.Path(file_okay=False), - required=True, help='Source of your photo library.') +@click.option('--path', type=click.Path(file_okay=False), + required=True, help='Path of your photo library.') @click.option('--debug', default=False, is_flag=True, help='Override the value in constants.py with True.') def _generate_db(path, debug): @@ -164,29 +164,31 @@ def _generate_db(path, debug): """ constants.debug = debug result = Result() - source = os.path.abspath(os.path.expanduser(source)) + path = os.path.abspath(os.path.expanduser(path)) - if not os.path.isdir(source): - log.error('Source is not a valid directory %s' % source) + if not os.path.isdir(path): + log.error('path is not a valid directory %s' % path) sys.exit(1) db = Db(path) db.backup_hash_db() db.reset_hash_db() - for current_file in FILESYSTEM.get_all_files(source): + for current_file in FILESYSTEM.get_all_files(path): result.append((current_file, True)) db.add_hash(db.checksum(current_file), current_file) log.progress() - + db.update_hash_db() log.progress('', True) result.write() @click.command('verify') +@click.option('--path', type=click.Path(file_okay=False), + required=True, help='Path of your photo library.') @click.option('--debug', default=False, is_flag=True, help='Override the value in constants.py with True.') -def _verify(debug): +def _verify(path, debug): constants.debug = debug result = Result() db = Db(path) @@ -216,7 +218,7 @@ def update_location(media, file_path, location_name, db): if location_coords and 'latitude' in location_coords and \ 'longitude' in location_coords: location_status = media.set_location(location_coords[ - 'latitude'], location_coords['longitude']) + 'latitude'], location_coords['longitude'], file_path) if not location_status: log.error('Failed to update location') log.all(('{"source":"%s",' % file_path, @@ -307,7 +309,7 @@ def _update(album, location, time, title, paths, debug): update_time(media, current_file, time) updated = True if album: - media.set_album(album) + media.set_album(album, current_file) updated = True import ipdb; ipdb.set_trace() diff --git a/elodie/filesystem.py b/elodie/filesystem.py index 87b0fd5..d334a23 100644 --- a/elodie/filesystem.py +++ b/elodie/filesystem.py @@ -17,7 +17,7 @@ from elodie.config import load_config from elodie import constants from elodie.localstorage import Db -from elodie.media import base +from elodie.media import media from elodie.plugins.plugins import Plugins class FileSystem(object): @@ -94,7 +94,7 @@ class FileSystem(object): # If extensions is None then we get all supported extensions if not extensions: extensions = set() - subclasses = base.get_all_subclasses() + subclasses = media.get_all_subclasses() for cls in subclasses: extensions.update(cls.extensions) @@ -679,7 +679,9 @@ class FileSystem(object): if album_from_folder: media.set_album_from_folder(dest_path) - db.add_hash(checksum, dest_path) + # get checksum of dest file + dest_checksum = db.checksum(dest_path) + db.add_hash(dest_checksum, dest_path) db.update_hash_db() # Run `after()` for every loaded plugin and if any of them raise an exception diff --git a/elodie/media/audio.py b/elodie/media/audio.py index b79123f..6b26a30 100644 --- a/elodie/media/audio.py +++ b/elodie/media/audio.py @@ -1,15 +1,15 @@ """ The audio module contains classes specifically for dealing with audio files. -The :class:`Audio` class inherits from the :class:`~elodie.media.video.Video` +The :class:`Audio` class inherits from the :class:`~elodie.media.Media` class. .. moduleauthor:: Jaisen Mathai """ -from .video import Video +from .media import Media -class Audio(Video): +class Audio(Media): """An audio object. @@ -22,4 +22,7 @@ class Audio(Video): extensions = ('m4a',) def __init__(self, source=None): - super(Audio, self).__init__(source) + super().__init__(source) + + def is_valid(self): + return super().is_valid() diff --git a/elodie/media/base.py b/elodie/media/base.py deleted file mode 100644 index 7132345..0000000 --- a/elodie/media/base.py +++ /dev/null @@ -1,264 +0,0 @@ -""" -The base module provides a base :class:`Base` class for all objects that -are tracked by Elodie. The Base class provides some base functionality used -by all the media types, but isn't itself used to represent anything. Its -sub-classes (:class:`~elodie.media.audio.Audio`, -:class:`~elodie.media.photo.Photo`, :class:`~elodie.media.video.Video`) -are used to represent the actual files. - -.. moduleauthor:: Jaisen Mathai -""" - -import mimetypes -import os - -try: # Py3k compatibility - basestring -except NameError: - basestring = (bytes, str) - - -class Base(object): - - """The base class for all media objects. - - :param str source: The fully qualified path to the video file. - """ - - __name__ = 'Base' - - PHOTO = ('arw', 'cr2', 'dng', 'gif', 'heic', 'jpeg', 'jpg', 'nef', 'png', 'rw2') - AUDIO = ('m4a',) - VIDEO = ('avi', 'm4v', 'mov', 'mp4', 'mpg', 'mpeg', '3gp', 'mts') - - extensions = PHOTO + AUDIO + VIDEO - - def __init__(self, source=None): - self.source = source - self.reset_cache() - - def format_metadata(self, **kwargs): - """Method to consistently return a populated metadata dictionary. - - :returns: dict - """ - - def get_album(self): - """Base method for getting an album - - :returns: None - """ - return None - - def get_file_path(self): - """Get the full path to the video. - - :returns: string - """ - return self.source - - def get_coordinate(self, type): - return None - - def get_extension(self): - """Get the file extension as a lowercased string. - - :returns: string or None for a non-video - """ - if(not self.is_valid()): - return None - - source = self.source - return os.path.splitext(source)[1][1:].lower() - - def get_camera_make(self): - return None - - def get_camera_model(self): - return None - - def get_metadata(self, update_cache=False): - """Get a dictionary of metadata for any file. - - All keys will be present and have a value of None if not obtained. - - :returns: dict or None for non-text files - """ - if(not self.is_valid()): - return None - - if(isinstance(self.metadata, dict) and update_cache is False): - return self.metadata - - source = self.source - folder = os.path.basename(os.path.dirname(source)) - album = self.get_album() - album_from_folder = True - if album_from_folder and (album is None or album == ''): - album = folder - - self.metadata = { - 'date_original': self.get_date_attribute(self.date_original), - 'date_created': self.get_date_attribute(self.date_created), - 'date_modified': self.get_date_attribute(self.date_modified), - 'camera_make': self.get_camera_make(), - 'camera_model': self.get_camera_model(), - 'latitude': self.get_coordinate('latitude'), - 'longitude': self.get_coordinate('longitude'), - 'album': album, - 'title': self.get_title(), - 'mime_type': self.get_mimetype(), - 'original_name': self.get_original_name(), - 'base_name': folder, - 'extension': self.get_extension(), - 'directory_path': os.path.dirname(source) - } - - return self.metadata - - def get_mimetype(self): - """Get the mimetype of the file. - - :returns: str or None for unsupported files. - """ - if(not self.is_valid()): - return None - - source = self.source - mimetype = mimetypes.guess_type(source) - if(mimetype is None): - return None - - 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 - - :returns: None - """ - return None - - def is_valid(self): - """Check the file extension against valid file extensions. - - The list of valid file extensions come from self.extensions. - - :returns: bool - """ - source = self.source - return os.path.splitext(source)[1][1:].lower() in self.extensions - - def reset_cache(self): - """Resets any internal cache - """ - self.metadata = None - - def set_album(self, name): - """Base method for setting the album of a file - - :returns: None - """ - return None - - def set_album_from_folder(self, path): - """Set the album attribute based on the leaf folder name - - :returns: bool - """ - metadata = self.get_metadata() - - # If this file has an album already set we do not overwrite EXIF - if(not isinstance(metadata, dict) or metadata['album'] is not None): - return False - - folder = os.path.basename(metadata['directory_path']) - # If folder is empty we skip - if(len(folder) == 0): - return False - - self.set_album(folder) - if status == False: - return False - return True - - def set_metadata_basename(self, new_basename): - """Update the basename attribute in the metadata dict for this instance. - - This is used for when we update the EXIF title of a media file. Since - that determines the name of a file if we update the title of a file - more than once it appends to the file name. - - i.e. 2015-12-31_00-00-00-my-first-title-my-second-title.jpg - - :param str new_basename: New basename of file (with the old title - removed). - """ - self.get_metadata() - self.metadata['base_name'] = new_basename - - def set_metadata(self, **kwargs): - """Method to manually update attributes in metadata. - - :params dict kwargs: Named parameters to update. - """ - metadata = self.get_metadata() - for key in kwargs: - 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. - """ - if not isinstance(_file, basestring) or not os.path.isfile(_file): - return None - - extension = os.path.splitext(_file)[1][1:].lower() - - if len(extension) > 0: - for i in classes: - if(extension in i.extensions): - return i(_file) - - return None - - @classmethod - def get_valid_extensions(cls): - """Static method to access static extensions variable. - - :returns: tuple(str) - """ - return cls.extensions - - -def get_all_subclasses(cls=None): - """Module method to get all subclasses of Base. - """ - subclasses = set() - - this_class = Base - if cls is not None: - this_class = cls - - subclasses.add(this_class) - - this_class_subclasses = this_class.__subclasses__() - for child_class in this_class_subclasses: - subclasses.update(get_all_subclasses(child_class)) - - return subclasses diff --git a/elodie/media/media.py b/elodie/media/media.py index f5217a8..b24fc54 100644 --- a/elodie/media/media.py +++ b/elodie/media/media.py @@ -1,14 +1,14 @@ """ The media module provides a base :class:`Media` class for media objects that are tracked by Elodie. The Media class provides some base functionality used -by all the media types, but isn't itself used to represent anything. Its -sub-classes (:class:`~elodie.media.audio.Audio`, -:class:`~elodie.media.photo.Photo`, and :class:`~elodie.media.video.Video`) +by all the media types. Its sub-classes (:class:`~elodie.media.Audio`, +:class:`~elodie.media.Photo`, and :class:`~elodie.media.Video`) are used to represent the actual files. .. moduleauthor:: Jaisen Mathai """ +import mimetypes import os import six @@ -17,11 +17,17 @@ from elodie import log from dateutil.parser import parse import re from elodie.external.pyexiftool import ExifTool -from elodie.media.base import Base -class Media(Base): +# TODO remove +# try: # Py3k compatibility +# basestring +# except NameError: +# basestring = (bytes, str) - """The base class for all media objects. + +class Media(): + + """The media class for all media objects. :param str source: The fully qualified path to the video file. """ @@ -33,23 +39,221 @@ class Media(Base): 'longitude': 'longitude_ref' } - def __init__(self, source=None): - super(Media, self).__init__(source) - self.date_original = ['EXIF:DateTimeOriginal'] - self.date_created = ['EXIF:CreateDate', 'QuickTime:CreateDate'] + PHOTO = ('arw', 'cr2', 'dng', 'gif', 'heic', 'jpeg', 'jpg', 'nef', 'png', 'rw2') + AUDIO = ('m4a',) + VIDEO = ('avi', 'm4v', 'mov', 'mp4', 'mpg', 'mpeg', '3gp', 'mts') + + extensions = PHOTO + AUDIO + VIDEO + + + def __init__(self, sources=None): + self.source = sources + self.reset_cache() + self.date_original = [ + 'EXIF:DateTimeOriginal', + 'H264:DateTimeOriginal', + 'QuickTime:ContentCreateDate' + ] + self.date_created = [ + 'EXIF:CreateDate', + 'QuickTime:CreationDate', + 'QuickTime:CreateDate', + 'QuickTime:CreationDate-und-US', + 'QuickTime:MediaCreateDate' + ] self.date_modified = ['File:FileModifyDate', 'QuickTime:ModifyDate'] self.camera_make_keys = ['EXIF:Make', 'QuickTime:Make'] self.camera_model_keys = ['EXIF:Model', 'QuickTime:Model'] self.album_keys = ['XMP-xmpDM:Album', 'XMP:Album'] - self.title_key = 'XMP:Title' - self.latitude_keys = ['EXIF:GPSLatitude'] - self.longitude_keys = ['EXIF:GPSLongitude'] + self.title_keys = ['XMP:Title', 'XMP:DisplayName'] + self.latitude_keys = [ + 'EXIF:GPSLatitude', + 'XMP:GPSLatitude', + # 'QuickTime:GPSLatitude', + 'Composite:GPSLatitude' + ] + self.longitude_keys = [ + 'EXIF:GPSLongitude', + 'XMP:GPSLongitude', + # 'QuickTime:GPSLongitude', + 'Composite: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.metadata = None self.exif_metadata = None + + def format_metadata(self, **kwargs): + """Method to consistently return a populated metadata dictionary. + + :returns: dict + """ + + def get_file_path(self): + """Get the full path to the video. + + :returns: string + """ + return self.source + + + def get_extension(self): + """Get the file extension as a lowercased string. + + :returns: string or None for a non-video + """ + if(not self.is_valid()): + return None + + source = self.source + return os.path.splitext(source)[1][1:].lower() + + + def get_metadata(self, update_cache=False, album_from_folder=False): + """Get a dictionary of metadata for any file. + + All keys will be present and have a value of None if not obtained. + + :returns: dict or None for non-text files + """ + if(not self.is_valid()): + return None + + if(isinstance(self.metadata, dict) and update_cache is False): + return self.metadata + + source = self.source + folder = os.path.basename(os.path.dirname(source)) + album = self.get_album() + if album_from_folder and (album is None or album == ''): + album = folder + + self.metadata = { + 'date_original': self.get_date_attribute(self.date_original), + 'date_created': self.get_date_attribute(self.date_created), + 'date_modified': self.get_date_attribute(self.date_modified), + 'camera_make': self.get_camera_make(), + 'camera_model': self.get_camera_model(), + 'latitude': self.get_coordinate('latitude'), + 'longitude': self.get_coordinate('longitude'), + 'album': album, + 'title': self.get_title(), + 'mime_type': self.get_mimetype(), + 'original_name': self.get_original_name(), + 'base_name': os.path.basename(os.path.splitext(source)[0]), + 'extension': self.get_extension(), + 'directory_path': os.path.dirname(source) + } + + return self.metadata + + + def get_mimetype(self): + """Get the mimetype of the file. + + :returns: str or None for unsupported files. + """ + if(not self.is_valid()): + return None + + source = self.source + mimetype = mimetypes.guess_type(source) + if(mimetype is None): + return None + + return mimetype[0] + + + def is_valid(self): + """Check the file extension against valid file extensions. + + The list of valid file extensions come from self.extensions. + + :returns: bool + """ + source = self.source + return os.path.splitext(source)[1][1:].lower() in self.extensions + + + def set_album_from_folder(self, path): + """Set the album attribute based on the leaf folder name + + :returns: bool + """ + metadata = self.get_metadata() + + # If this file has an album already set we do not overwrite EXIF + if(not isinstance(metadata, dict) or metadata['album'] is not None): + return False + + folder = os.path.basename(metadata['directory_path']) + # If folder is empty we skip + if(len(folder) == 0): + return False + + status = self.set_album(folder, path) + if status == False: + return False + return True + + + def set_metadata_basename(self, new_basename): + """Update the basename attribute in the metadata dict for this instance. + + This is used for when we update the EXIF title of a media file. Since + that determines the name of a file if we update the title of a file + more than once it appends to the file name. + + i.e. 2015-12-31_00-00-00-my-first-title-my-second-title.jpg + + :param str new_basename: New basename of file (with the old title + removed). + """ + self.get_metadata() + self.metadata['base_name'] = new_basename + + + def set_metadata(self, **kwargs): + """Method to manually update attributes in metadata. + + :params dict kwargs: Named parameters to update. + """ + metadata = self.get_metadata() + for key in kwargs: + if(key in metadata): + self.metadata[key] = kwargs[key] + + + @classmethod + def get_class_by_file(cls, _file, classes): + """Static method to get a media object by file. + """ + basestring = (bytes, str) + if not isinstance(_file, basestring) or not os.path.isfile(_file): + return None + + extension = os.path.splitext(_file)[1][1:].lower() + + if len(extension) > 0: + for i in classes: + if(extension in i.extensions): + return i(_file) + + return None + + + @classmethod + def get_valid_extensions(cls): + """Static method to access static extensions variable. + + :returns: tuple(str) + """ + return cls.extensions + + def get_album(self): """Get album from EXIF @@ -68,6 +272,7 @@ class Media(Base): return None + def get_coordinate(self, type='latitude'): """Get latitude or longitude of media from EXIF @@ -114,6 +319,7 @@ class Media(Base): return None + def get_exiftool_attributes(self): """Get attributes for the media object from exiftool. @@ -185,6 +391,7 @@ class Media(Base): return None + def get_camera_model(self): """Get the camera make stored in EXIF. @@ -204,6 +411,7 @@ class Media(Base): return None + def get_original_name(self): """Get the original name stored in EXIF. @@ -222,6 +430,7 @@ class Media(Base): return exiftool_attributes[self.original_name_key] + def get_title(self): """Get the title for a photo of video @@ -235,17 +444,19 @@ class Media(Base): if exiftool_attributes is None: return None - if(self.title_key not in exiftool_attributes): - return None + for title_key in self.title_keys: + if title_key in exiftool_attributes: + return exiftool_attributes[title_key] + + return None - return exiftool_attributes[self.title_key] def reset_cache(self): """Resets any internal cache """ self.exiftool_attributes = None self.exif_metadata = None - super(Media, self).reset_cache() + def set_album(self, name, path): """Set album EXIF tag if not already set. @@ -255,12 +466,16 @@ class Media(Base): if self.get_album() is not None: return None - tags = {self.album_keys[0]: name} - status = ExifTool().set_tags(tags, path) + tags = {} + for key in self.album_keys: + tags[key] = name + status = self.__set_tags(tags, path) self.reset_cache() - return status != '' - def set_date_original(self, time): + return status + + + def set_date_original(self, time, path): """Set the date/time a photo was taken. :param datetime time: datetime object of when the photo was taken @@ -274,31 +489,39 @@ class Media(Base): for key in self.date_original: tags[key] = formatted_time - status = self.__set_tags(tags) + status = self.__set_tags(tags, path) if status == False: # exif attribute date_original d'ont exist for key in self.date_created: tags[key] = formatted_time - status = self.__set_tags(tags) + status = self.__set_tags(tags, path) self.reset_cache() return status - def set_location(self, latitude, longitude): + + def set_location(self, latitude, longitude, path): if(not self.is_valid()): return None # The lat/lon _keys array has an order of precedence. # The first key is writable and we will give the writable # key precence when reading. - tags = { - self.latitude_keys[0]: latitude, - self.longitude_keys[0]: longitude, - } + # TODO check + # tags = { + # self.latitude_keys[0]: latitude, + # self.longitude_keys[0]: longitude, + # } + tags = {} + for key in self.latitude_keys: + tags[key] = latitude + for key in self.longitude_keys: + tags[key] = longitude # If self.set_gps_ref == True then it means we are writing an EXIF # GPS tag which requires us to set the reference key. # That's because the lat/lon are absolute values. + # TODO set_gps_ref = False for Video ? if self.set_gps_ref: if latitude < 0: tags[self.latitude_ref_key] = 'S' @@ -306,12 +529,13 @@ class Media(Base): if longitude < 0: tags[self.longitude_ref_key] = 'W' - status = self.__set_tags(tags) + status = self.__set_tags(tags, path) self.reset_cache() return status - def set_original_name(self, path): + + def set_original_name(self, path, name=None): """Sets the original name EXIF tag if not already set. :returns: True, False, None @@ -320,14 +544,17 @@ class Media(Base): if self.get_original_name() is not None: return None - name = os.path.basename(path) + if name == None: + name = os.path.basename(self.source) tags = {self.original_name_key: name} - status = ExifTool().set_tags(tags, path) + status = self.__set_tags(tags, path) self.reset_cache() - return status != '' - def set_title(self, title): + return status + + + def set_title(self, title, path): """Set title for a photo. :param str title: Title of the photo. @@ -339,23 +566,43 @@ class Media(Base): if(title is None): return None - tags = {self.title_key: title} - status = self.__set_tags(tags) + tags = {} + for key in self.title_keys: + tags[key] = title + status = self.__set_tags(tags, path) self.reset_cache() return status - def __set_tags(self, tags): + + def __set_tags(self, tags, path): if(not self.is_valid()): return None - source = self.source - status = '' - status = ExifTool().set_tags(tags,source) + status = ExifTool().set_tags(tags, path) if status.decode().find('unchanged') != -1 or status == '': return False if status.decode().find('error') != -1: return False return True + + +def get_all_subclasses(cls=None): + """Module method to get all subclasses of Media. + """ + subclasses = set() + + this_class = Media + if cls is not None: + this_class = cls + + subclasses.add(this_class) + + this_class_subclasses = this_class.__subclasses__() + for child_class in this_class_subclasses: + subclasses.update(get_all_subclasses(child_class)) + + return subclasses + diff --git a/elodie/media/photo.py b/elodie/media/photo.py index 2f12796..a655c27 100644 --- a/elodie/media/photo.py +++ b/elodie/media/photo.py @@ -25,7 +25,7 @@ class Photo(Media): extensions = ('arw', 'cr2', 'dng', 'gif', 'heic', 'jpeg', 'jpg', 'nef', 'png', 'rw2') def __init__(self, source=None): - super(Photo, self).__init__(source) + super().__init__(source) # We only want to parse EXIF once so we store it here self.exif = None @@ -79,5 +79,5 @@ class Photo(Media): if(im.format is None): return False - + return extension in self.extensions diff --git a/elodie/media/video.py b/elodie/media/video.py index 28580ed..fde22f5 100644 --- a/elodie/media/video.py +++ b/elodie/media/video.py @@ -28,31 +28,9 @@ class Video(Media): extensions = ('avi', 'm4v', 'mov', 'mp4', 'mpg', 'mpeg', '3gp', 'mts') def __init__(self, source=None): - super(Video, self).__init__(source) - self.date_original = [ - 'EXIF:DateTimeOriginal', - 'H264:DateTimeOriginal', - 'QuickTime:ContentCreateDate' - ] - self.date_created = [ - 'EXIF:CreateDate', - 'QuickTime:CreationDate', - 'QuickTime:CreateDate', - 'QuickTime:CreationDate-und-US', - 'QuickTime:MediaCreateDate' - ] - self.date_modified = ['File:FileModifyDate'] - self.title_key = 'XMP:DisplayName' - self.latitude_keys = [ - 'XMP:GPSLatitude', - # 'QuickTime:GPSLatitude', - 'Composite:GPSLatitude' - ] - self.longitude_keys = [ - 'XMP:GPSLongitude', - # 'QuickTime:GPSLongitude', - 'Composite:GPSLongitude' - ] - self.latitude_ref_key = 'EXIF:GPSLatitudeRef' - self.longitude_ref_key = 'EXIF:GPSLongitudeRef' - self.set_gps_ref = False + super().__init__(source) + # self.set_gps_ref = False + + + def is_valid(self): + return super().is_valid() diff --git a/elodie/tools/add_original_name.py b/elodie/tools/add_original_name.py index ee66ba0..0a52dc8 100644 --- a/elodie/tools/add_original_name.py +++ b/elodie/tools/add_original_name.py @@ -9,8 +9,7 @@ from elodie import log from elodie.compatability import _decode from elodie.filesystem import FileSystem from elodie.localstorage import Db -from elodie.media.base import Base, get_all_subclasses -from elodie.media.media import Media +from elodie.media.media import Media, get_all_subclasses from elodie.media.audio import Audio from elodie.media.photo import Photo from elodie.media.video import Video diff --git a/tests/media/base_test.py b/tests/media/base_test.py deleted file mode 100644 index e87cc8d..0000000 --- a/tests/media/base_test.py +++ /dev/null @@ -1,138 +0,0 @@ -# Project imports -import os -import sys - -import hashlib -import random -import re -import shutil -import string -import tempfile -import time - -sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))))) -sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) - -import helper -from elodie.media.base import Base, get_all_subclasses -from elodie.media.media import Media -from elodie.media.audio import Audio -from elodie.media.photo import Photo -from elodie.media.video import Video - -os.environ['TZ'] = 'GMT' - -setup_module = helper.setup_module -teardown_module = helper.teardown_module - -def test_get_all_subclasses(): - subclasses = get_all_subclasses(Base) - expected = {Media, Base, Photo, Video, Audio} - assert subclasses == expected, subclasses - -def test_get_class_by_file_without_extension(): - base_file = helper.get_file('withoutextension') - - cls = Base.get_class_by_file(base_file, [Audio, Photo, Video]) - - 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() - - base_file = helper.get_file('invalid.jpg') - origin = '%s/invalid.jpg' % folder - - shutil.copyfile(base_file, origin) - - media = Media(origin) - status = media.set_album_from_folder() - - assert status == False, status - -def test_set_album_from_folder(): - temporary_folder, folder = helper.create_working_folder() - - origin = '%s/photo.jpg' % folder - shutil.copyfile(helper.get_file('plain.jpg'), origin) - - photo = Photo(origin) - metadata = photo.get_metadata() - - assert metadata['album'] is None, metadata['album'] - - new_album_name = os.path.split(folder)[1] - status = photo.set_album_from_folder() - - assert status == True, status - - photo_new = Photo(origin) - metadata_new = photo_new.get_metadata() - - shutil.rmtree(folder) - - assert metadata_new['album'] == new_album_name, metadata_new['album'] - -def test_set_metadata(): - temporary_folder, folder = helper.create_working_folder() - - origin = '%s/photo.jpg' % folder - shutil.copyfile(helper.get_file('plain.jpg'), origin) - - photo = Photo(origin) - - metadata = photo.get_metadata() - - assert metadata['title'] == None, metadata['title'] - - new_title = 'Some Title' - photo.set_metadata(title = new_title) - - new_metadata = photo.get_metadata() - - assert new_metadata['title'] == new_title, new_metadata['title'] - -def test_set_metadata_basename(): - temporary_folder, folder = helper.create_working_folder() - - origin = '%s/photo.jpg' % folder - shutil.copyfile(helper.get_file('plain.jpg'), origin) - - photo = Photo(origin) - - metadata = photo.get_metadata() - - assert metadata['base_name'] == 'photo', metadata['base_name'] - - new_basename = 'Some Base Name' - photo.set_metadata_basename(new_basename) - - new_metadata = photo.get_metadata() - - assert new_metadata['base_name'] == new_basename, new_metadata['base_name'] diff --git a/tests/media/audio_test.py b/tests/media/test_audio.py similarity index 93% rename from tests/media/audio_test.py rename to tests/media/test_audio.py index 431a8da..025aaec 100644 --- a/tests/media/audio_test.py +++ b/tests/media/test_audio.py @@ -8,7 +8,6 @@ import tempfile import time from datetime import datetime -sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))))) sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) import helper @@ -100,16 +99,16 @@ def test_set_date_original(): origin = '%s/audio.m4a' % folder shutil.copyfile(helper.get_file('audio.m4a'), origin) - media = Media(origin) + audio = Audio(origin) date = datetime(2013, 9, 30, 7, 6, 5) - status = media.set_date_original(date) + status = audio.set_date_original(date) assert status == True, status audio_new = Audio(origin) - metadata = audio_new.get_metadata() + metadata = audio_new.get_metadata(update_cache=True) - date_original = metadata['date_created'] + date_original = metadata['date_original'] shutil.rmtree(folder) @@ -134,7 +133,7 @@ def test_set_location(): assert status == True, status audio_new = Audio(origin) - metadata = audio_new.get_metadata() + metadata = audio_new.get_metadata(update_cache=True) shutil.rmtree(folder) @@ -160,7 +159,7 @@ def test_set_location_minus(): assert status == True, status audio_new = Audio(origin) - metadata = audio_new.get_metadata() + metadata = audio_new.get_metadata(update_cache=True) shutil.rmtree(folder) @@ -202,7 +201,7 @@ def test_set_title_non_ascii(): assert status == True, status audio_new = Audio(origin) - metadata = audio_new.get_metadata() + metadata = audio_new.get_metadata(update_cache=True) shutil.rmtree(folder) diff --git a/tests/media/media_test.py b/tests/media/test_media.py similarity index 62% rename from tests/media/media_test.py rename to tests/media/test_media.py index f435bb4..a67313e 100644 --- a/tests/media/media_test.py +++ b/tests/media/test_media.py @@ -9,13 +9,13 @@ import shutil import string import tempfile import time +import unittest -sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))))) sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) import helper +from elodie.media.media import Media, get_all_subclasses from elodie.media.audio import Audio -from elodie.media.media import Media from elodie.media.photo import Photo from elodie.media.video import Video @@ -24,6 +24,119 @@ os.environ['TZ'] = 'GMT' setup_module = helper.setup_module teardown_module = helper.teardown_module +def test_get_all_subclasses(): + subclasses = get_all_subclasses(Media) + expected = {Media, Photo, Video, Audio} + assert subclasses == expected, subclasses + +def test_get_class_by_file_without_extension(): + base_file = helper.get_file('withoutextension') + + cls = Media.get_class_by_file(base_file, [Audio, Photo, Video]) + + 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() + + base_file = helper.get_file('invalid.jpg') + origin = '%s/invalid.jpg' % folder + + shutil.copyfile(base_file, origin) + + media = Media(origin) + status = media.set_album_from_folder(origin) + + assert status == False, status + +def test_set_album_from_folder(): + temporary_folder, folder = helper.create_working_folder() + + origin = '%s/photo.jpg' % folder + shutil.copyfile(helper.get_file('plain.jpg'), origin) + + media = Media(origin) + metadata = media.get_metadata() + + assert metadata['album'] is None, metadata['album'] + + new_album_name = os.path.split(folder)[1] + status = media.set_album_from_folder(origin) + + assert status == True, status + + media_new = Media(origin) + metadata_new = media_new.get_metadata(update_cache=True) + + shutil.rmtree(folder) + + assert metadata_new['album'] == new_album_name, metadata_new['album'] + +def test_set_metadata(): + temporary_folder, folder = helper.create_working_folder() + + origin = '%s/photo.jpg' % folder + shutil.copyfile(helper.get_file('plain.jpg'), origin) + + media = Media(origin) + + metadata = media.get_metadata() + + assert metadata['title'] == None, metadata['title'] + + new_title = 'Some Title' + media.set_metadata(title = new_title) + + new_metadata = media.get_metadata() + + assert new_metadata['title'] == new_title, new_metadata['title'] + +def test_set_metadata_basename(): + temporary_folder, folder = helper.create_working_folder() + + origin = '%s/photo.jpg' % folder + shutil.copyfile(helper.get_file('plain.jpg'), origin) + + media = Media(origin) + + metadata = media.get_metadata() + + assert metadata['base_name'] == 'photo', metadata['base_name'] + + new_basename = 'Some Base Name' + media.set_metadata_basename(new_basename) + + new_metadata = media.get_metadata() + + assert new_metadata['base_name'] == new_basename, new_metadata['base_name'] + + def test_get_file_path(): media = Media(helper.get_file('plain.jpg')) path = media.get_file_path() @@ -63,42 +176,16 @@ 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() + result = media.set_original_name(origin) assert result is None, result @@ -107,13 +194,13 @@ def test_set_original_name_when_does_not_exist(): 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() + result = media.set_original_name(origin) + metadata_after = media.get_metadata(update_cache=True) assert metadata_before['original_name'] is None, metadata_before assert metadata_after['original_name'] == 'plain.jpg', metadata_after @@ -124,7 +211,7 @@ def test_set_original_name_with_arg(): origin = '%s/%s' % (folder, 'plain.jpg') file = helper.get_file('plain.jpg') - + shutil.copyfile(file, origin) new_name = helper.random_string(15) @@ -132,7 +219,7 @@ def test_set_original_name_with_arg(): media = Media.get_class_by_file(origin, [Photo]) metadata_before = media.get_metadata() result = media.set_original_name(new_name) - metadata_after = media.get_metadata() + metadata_after = media.get_metadata(update_cache=True) assert metadata_before['original_name'] is None, metadata_before assert metadata_after['original_name'] == new_name, metadata_after @@ -154,10 +241,10 @@ def test_set_original_name(): shutil.copyfile(file_path, origin) - media = Media.get_class_by_file(origin, [Audio, Media, Photo, Video]) + media = Media.get_class_by_file(origin, [Audio, Photo, Video]) metadata = media.get_metadata() - media.set_original_name() - metadata_updated = media.get_metadata() + media.set_original_name(origin) + metadata_updated = media.get_metadata(update_cache=True) shutil.rmtree(folder) diff --git a/tests/media/photo_test.py b/tests/media/test_photo.py similarity index 93% rename from tests/media/photo_test.py rename to tests/media/test_photo.py index fdda0c3..724ebe9 100644 --- a/tests/media/photo_test.py +++ b/tests/media/test_photo.py @@ -10,7 +10,6 @@ import time from nose.plugins.skip import SkipTest -sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))))) sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) import helper @@ -122,8 +121,8 @@ def test_get_coordinates_with_null_coordinate(): assert longitude is None, longitude def test_get_date_original(): - media = Media(helper.get_file('plain.jpg')) - date_original = media.get_date_attribute(['EXIF:DateTimeOriginal']) + photo = Photo(helper.get_file('plain.jpg')) + date_original = photo.get_date_attribute(['EXIF:DateTimeOriginal']) #assert date_original == (2015, 12, 5, 0, 59, 26, 5, 339, 0), date_original assert date_original == datetime(2015, 12, 5, 0, 59, 26), date_original @@ -174,12 +173,12 @@ def test_set_album(): assert metadata['album'] is None, metadata['album'] - status = photo.set_album('Test Album') + status = photo.set_album('Test Album', origin) assert status == True, status photo_new = Photo(origin) - metadata_new = photo_new.get_metadata() + metadata_new = photo_new.get_metadata(update_cache=True) shutil.rmtree(folder) @@ -193,14 +192,14 @@ def test_set_date_original_with_missing_datetimeoriginal(): origin = '%s/photo.jpg' % folder shutil.copyfile(helper.get_file('no-exif.jpg'), origin) - media = Media(origin) + photo = Photo(origin) time = datetime(2013, 9, 30, 7, 6, 5) - status = media.set_date_original(time) + status = photo.set_date_original(time) assert status == True, status photo_new = Photo(origin) - metadata = photo_new.get_metadata() + metadata = photo_new.get_metadata(update_cache=True) date_original = metadata['date_original'] @@ -216,13 +215,13 @@ def test_set_date_original(): origin = '%s/photo.jpg' % folder shutil.copyfile(helper.get_file('plain.jpg'), origin) - media = Media(origin) - status = media.set_date_original(datetime(2013, 9, 30, 7, 6, 5)) + photo = Photo(origin) + status = photo.set_date_original(datetime(2013, 9, 30, 7, 6, 5)) assert status == True, status - media_new = Media(origin) - metadata = media_new.get_metadata() + photo_new = Photo(origin) + metadata = photo_new.get_metadata(update_cache=True) date_original = metadata['date_original'] @@ -250,7 +249,7 @@ def test_set_location(): assert status == True, status photo_new = Photo(origin) - metadata = photo_new.get_metadata() + metadata = photo_new.get_metadata(update_cache=True) shutil.rmtree(folder) @@ -276,7 +275,7 @@ def test_set_location_minus(): assert status == True, status photo_new = Photo(origin) - metadata = photo_new.get_metadata() + metadata = photo_new.get_metadata(update_cache=True) shutil.rmtree(folder) @@ -297,7 +296,7 @@ def test_set_title(): assert status == True, status photo_new = Photo(origin) - metadata = photo_new.get_metadata() + metadata = photo_new.get_metadata(update_cache=True) shutil.rmtree(folder) @@ -318,7 +317,7 @@ def test_set_title_non_ascii(): assert status == True, status photo_new = Photo(origin) - metadata = photo_new.get_metadata() + metadata = photo_new.get_metadata(update_cache=True) shutil.rmtree(folder) @@ -395,7 +394,7 @@ def _test_photo_type_set(type, date): assert status == True, status photo_new = Photo(origin) - metadata = photo_new.get_metadata() + metadata = photo_new.get_metadata(update_cache=True) shutil.rmtree(folder) diff --git a/tests/media/video_test.py b/tests/media/test_video.py similarity index 96% rename from tests/media/video_test.py rename to tests/media/test_video.py index 8bc001e..f355b19 100644 --- a/tests/media/video_test.py +++ b/tests/media/test_video.py @@ -9,7 +9,6 @@ import time from datetime import datetime from dateutil.parser import parse -sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))))) sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) import helper @@ -102,7 +101,7 @@ def test_set_album(): assert metadata['album'] is None, metadata['album'] - status = video.set_album('Test Album') + status = video.set_album('Test Album', origin) assert status == True, status