From f27d2ef735a7e6ec8960fe62e2e29cef416d6d3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Dr=C3=A4s?= Date: Sun, 9 Oct 2016 23:11:28 +0200 Subject: [PATCH 1/6] Add files via upload --- filesystem.py | 295 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 filesystem.py diff --git a/filesystem.py b/filesystem.py new file mode 100644 index 0000000..b1c341e --- /dev/null +++ b/filesystem.py @@ -0,0 +1,295 @@ +""" +General file system methods. + +.. moduleauthor:: Jaisen Mathai +""" +from __future__ import print_function +from builtins import object + +import os +import re +import shutil +import time + +from elodie import geolocation +from elodie import constants +from elodie.localstorage import Db +from elodie.media.media import Media +from elodie.media.text import Text +from elodie.media.audio import Audio +from elodie.media.photo import Photo +from elodie.media.video import Video + + +class FileSystem(object): + + """A class for interacting with the file system.""" + + def create_directory(self, directory_path): + """Create a directory if it does not already exist. + + :param str directory_name: A fully qualified path of the + to create. + :returns: bool + """ + try: + if os.path.exists(directory_path): + return True + else: + os.makedirs(directory_path) + return True + except OSError: + # OSError is thrown for cases like no permission + pass + + return False + + def delete_directory_if_empty(self, directory_path): + """Delete a directory only if it's empty. + + Instead of checking first using `len([name for name in + os.listdir(directory_path)]) == 0`, we catch the OSError exception. + + :param str directory_name: A fully qualified path of the directory + to delete. + """ + try: + os.rmdir(directory_path) + return True + except OSError: + pass + + return False + + def get_all_files(self, path, extensions=None): + """Recursively get all files which match a path and extension. + + :param str path string: Path to start recursive file listing + :param tuple(str) extensions: File extensions to include (whitelist) + """ + files = [] + for dirname, dirnames, filenames in os.walk(path): + # print path to all filenames. + for filename in filenames: + if( + extensions is None or + filename.lower().endswith(extensions) + ): + files.append(os.path.join(dirname, filename)) + return files + + def get_current_directory(self): + """Get the current working directory. + + :returns: str + """ + return os.getcwd() + + def get_file_name(self, media): + """Generate file name for a photo or video using its metadata. + + We use an ISO8601-like format for the file name prefix. Instead of + colons as the separator for hours, minutes and seconds we use a hyphen. + https://en.wikipedia.org/wiki/ISO_8601#General_principles + + :param media: A Photo or Video instance + :type media: :class:`~elodie.media.photo.Photo` or + :class:`~elodie.media.video.Video` + :returns: str or None for non-photo or non-videos + """ + if(not media.is_valid()): + return None + + metadata = media.get_metadata() + if(metadata is None): + return None + + # If the file has EXIF title we use that in the file name + # (i.e. my-favorite-photo-img_1234.jpg) + # We want to remove the date prefix we add to the name. + # This helps when re-running the program on file which were already + # processed. + base_name = re.sub( + '^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}-', + '', + metadata['base_name'] + ) + if(len(base_name) == 0): + base_name = metadata['base_name'] + + if( + 'title' in metadata and + metadata['title'] is not None and + len(metadata['title']) > 0 + ): + title_sanitized = re.sub('\W+', '-', metadata['title'].strip()) + base_name = base_name.replace('-%s' % title_sanitized, '') + base_name = '%s-%s' % (base_name, title_sanitized) + + file_name = '%s-%s.%s' % ( + time.strftime( + '%Y-%m-%d_%H-%M-%S', + metadata['date_taken'] + ), + base_name, + metadata['extension']) + return file_name.lower() + + def get_folder_name_by_date(self, time_obj): + """Get date based folder name. + + :param time time_obj: Time object to be used to determine folder name. + :returns: str + """ + return time.strftime('%Y-%m-%b', time_obj) + + def get_folder_path(self, metadata): + """Get folder path by various parameters. + + :param time time_obj: Time object to be used to determine folder name. + :returns: str + """ + path = [] + if(metadata['date_taken'] is not None): + path.append(time.strftime('%Y-%m-%b', metadata['date_taken'])) + + if(metadata['album'] is not None): + path.append(metadata['album']) + elif( + metadata['latitude'] is not None and + metadata['longitude'] is not None + ): + place_name = geolocation.place_name( + metadata['latitude'], + metadata['longitude'] + ) + if(place_name is not None): + path.append(place_name) + + # if we don't have a 2nd level directory we use 'Unknown Location' + if(len(path) < 2): + path.append('Unknown Location') + + # return '/'.join(path[::-1]) + return os.path.join(*path) + + def process_file(self, _file, destination, media, **kwargs): + move = False + if('move' in kwargs): + move = kwargs['move'] + + allow_duplicate = False + if('allowDuplicate' in kwargs): + allow_duplicate = kwargs['allowDuplicate'] + + if(not media.is_valid()): + print('%s is not a valid media file. Skipping...' % _file) + return + + metadata = media.get_metadata() + + directory_name = self.get_folder_path(metadata) + + dest_directory = os.path.join(destination, directory_name) + file_name = self.get_file_name(media) + dest_path = os.path.join(dest_directory, file_name) + + db = Db() + checksum = db.checksum(_file) + if(checksum is None): + if(constants.debug is True): + print('Could not get checksum for %s. Skipping...' % _file) + return + + # If duplicates are not allowed and this hash exists in the db then we + # return + if(allow_duplicate is False and db.check_hash(checksum) is True): + if(constants.debug is True): + print('%s already exists at %s. Skipping...' % ( + _file, + db.get_hash(checksum) + )) + return + + self.create_directory(dest_directory) + + if(move is True): + stat = os.stat(_file) + shutil.move(_file, dest_path) + os.utime(dest_path, (stat.st_atime, stat.st_mtime)) + else: + shutil.copy(_file, dest_path) + self.set_date_from_filename(dest_path) + + db.add_hash(checksum, dest_path) + db.update_hash_db() + + return dest_path + + def set_date_from_filename(self, file): + """ Set the modification time on the file base on the file name. + """ + + date_taken = None + file_name = os.path.basename(file) + # Initialize date taken to what's returned from the metadata function. + # If the folder and file name follow a time format of + # YYYY-MM/DD-IMG_0001.JPG then we override the date_taken + (year, month, day) = [None] * 3 + year_month_day_match = re.search('(\d{4})-(\d{2})-(\d{2})', file_name) + if(year_month_day_match is not None): + (year, month, day) = year_month_day_match.groups() + + # check if the file system path indicated a date and if so we + # override the metadata value + if(year is not None and month is not None and day is not None): + date_taken = time.strptime( + '{}-{}-{}'.format(year, month, day), + '%Y-%m-%d' + ) + + os.utime(file, (time.time(), time.mktime(date_taken))) + + def set_date_from_path_video(self, video): + """Set the modification time on the file based on the file path. + + Noop if the path doesn't match the format YYYY-MM/DD-IMG_0001.JPG. + + :param elodie.media.video.Video video: An instance of Video. + """ + date_taken = None + + video_file_path = video.get_file_path() + + # Initialize date taken to what's returned from the metadata function. + # If the folder and file name follow a time format of + # YYYY-MM/DD-IMG_0001.JPG then we override the date_taken + (year, month, day) = [None] * 3 + directory = os.path.dirname(video_file_path) + # If the directory matches we get back a match with + # groups() = (year, month) + year_month_match = re.search('(\d{4})-(\d{2})', directory) + if(year_month_match is not None): + (year, month) = year_month_match.groups() + day_match = re.search( + '^(\d{2})', + os.path.basename(video.get_file_path()) + ) + if(day_match is not None): + day = day_match.group(1) + + # check if the file system path indicated a date and if so we + # override the metadata value + if(year is not None and month is not None): + if(day is not None): + date_taken = time.strptime( + '{}-{}-{}'.format(year, month, day), + '%Y-%m-%d' + ) + else: + date_taken = time.strptime( + '{}-{}'.format(year, month), + '%Y-%m' + ) + + os.utime(video_file_path, (time.time(), time.mktime(date_taken))) From 42c5e86f014c60259e286be6ca613a1d58243ed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Dr=C3=A4s?= Date: Sun, 9 Oct 2016 23:11:51 +0200 Subject: [PATCH 2/6] gh-139 Delete filesystem.py --- filesystem.py | 295 -------------------------------------------------- 1 file changed, 295 deletions(-) delete mode 100644 filesystem.py diff --git a/filesystem.py b/filesystem.py deleted file mode 100644 index b1c341e..0000000 --- a/filesystem.py +++ /dev/null @@ -1,295 +0,0 @@ -""" -General file system methods. - -.. moduleauthor:: Jaisen Mathai -""" -from __future__ import print_function -from builtins import object - -import os -import re -import shutil -import time - -from elodie import geolocation -from elodie import constants -from elodie.localstorage import Db -from elodie.media.media import Media -from elodie.media.text import Text -from elodie.media.audio import Audio -from elodie.media.photo import Photo -from elodie.media.video import Video - - -class FileSystem(object): - - """A class for interacting with the file system.""" - - def create_directory(self, directory_path): - """Create a directory if it does not already exist. - - :param str directory_name: A fully qualified path of the - to create. - :returns: bool - """ - try: - if os.path.exists(directory_path): - return True - else: - os.makedirs(directory_path) - return True - except OSError: - # OSError is thrown for cases like no permission - pass - - return False - - def delete_directory_if_empty(self, directory_path): - """Delete a directory only if it's empty. - - Instead of checking first using `len([name for name in - os.listdir(directory_path)]) == 0`, we catch the OSError exception. - - :param str directory_name: A fully qualified path of the directory - to delete. - """ - try: - os.rmdir(directory_path) - return True - except OSError: - pass - - return False - - def get_all_files(self, path, extensions=None): - """Recursively get all files which match a path and extension. - - :param str path string: Path to start recursive file listing - :param tuple(str) extensions: File extensions to include (whitelist) - """ - files = [] - for dirname, dirnames, filenames in os.walk(path): - # print path to all filenames. - for filename in filenames: - if( - extensions is None or - filename.lower().endswith(extensions) - ): - files.append(os.path.join(dirname, filename)) - return files - - def get_current_directory(self): - """Get the current working directory. - - :returns: str - """ - return os.getcwd() - - def get_file_name(self, media): - """Generate file name for a photo or video using its metadata. - - We use an ISO8601-like format for the file name prefix. Instead of - colons as the separator for hours, minutes and seconds we use a hyphen. - https://en.wikipedia.org/wiki/ISO_8601#General_principles - - :param media: A Photo or Video instance - :type media: :class:`~elodie.media.photo.Photo` or - :class:`~elodie.media.video.Video` - :returns: str or None for non-photo or non-videos - """ - if(not media.is_valid()): - return None - - metadata = media.get_metadata() - if(metadata is None): - return None - - # If the file has EXIF title we use that in the file name - # (i.e. my-favorite-photo-img_1234.jpg) - # We want to remove the date prefix we add to the name. - # This helps when re-running the program on file which were already - # processed. - base_name = re.sub( - '^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}-', - '', - metadata['base_name'] - ) - if(len(base_name) == 0): - base_name = metadata['base_name'] - - if( - 'title' in metadata and - metadata['title'] is not None and - len(metadata['title']) > 0 - ): - title_sanitized = re.sub('\W+', '-', metadata['title'].strip()) - base_name = base_name.replace('-%s' % title_sanitized, '') - base_name = '%s-%s' % (base_name, title_sanitized) - - file_name = '%s-%s.%s' % ( - time.strftime( - '%Y-%m-%d_%H-%M-%S', - metadata['date_taken'] - ), - base_name, - metadata['extension']) - return file_name.lower() - - def get_folder_name_by_date(self, time_obj): - """Get date based folder name. - - :param time time_obj: Time object to be used to determine folder name. - :returns: str - """ - return time.strftime('%Y-%m-%b', time_obj) - - def get_folder_path(self, metadata): - """Get folder path by various parameters. - - :param time time_obj: Time object to be used to determine folder name. - :returns: str - """ - path = [] - if(metadata['date_taken'] is not None): - path.append(time.strftime('%Y-%m-%b', metadata['date_taken'])) - - if(metadata['album'] is not None): - path.append(metadata['album']) - elif( - metadata['latitude'] is not None and - metadata['longitude'] is not None - ): - place_name = geolocation.place_name( - metadata['latitude'], - metadata['longitude'] - ) - if(place_name is not None): - path.append(place_name) - - # if we don't have a 2nd level directory we use 'Unknown Location' - if(len(path) < 2): - path.append('Unknown Location') - - # return '/'.join(path[::-1]) - return os.path.join(*path) - - def process_file(self, _file, destination, media, **kwargs): - move = False - if('move' in kwargs): - move = kwargs['move'] - - allow_duplicate = False - if('allowDuplicate' in kwargs): - allow_duplicate = kwargs['allowDuplicate'] - - if(not media.is_valid()): - print('%s is not a valid media file. Skipping...' % _file) - return - - metadata = media.get_metadata() - - directory_name = self.get_folder_path(metadata) - - dest_directory = os.path.join(destination, directory_name) - file_name = self.get_file_name(media) - dest_path = os.path.join(dest_directory, file_name) - - db = Db() - checksum = db.checksum(_file) - if(checksum is None): - if(constants.debug is True): - print('Could not get checksum for %s. Skipping...' % _file) - return - - # If duplicates are not allowed and this hash exists in the db then we - # return - if(allow_duplicate is False and db.check_hash(checksum) is True): - if(constants.debug is True): - print('%s already exists at %s. Skipping...' % ( - _file, - db.get_hash(checksum) - )) - return - - self.create_directory(dest_directory) - - if(move is True): - stat = os.stat(_file) - shutil.move(_file, dest_path) - os.utime(dest_path, (stat.st_atime, stat.st_mtime)) - else: - shutil.copy(_file, dest_path) - self.set_date_from_filename(dest_path) - - db.add_hash(checksum, dest_path) - db.update_hash_db() - - return dest_path - - def set_date_from_filename(self, file): - """ Set the modification time on the file base on the file name. - """ - - date_taken = None - file_name = os.path.basename(file) - # Initialize date taken to what's returned from the metadata function. - # If the folder and file name follow a time format of - # YYYY-MM/DD-IMG_0001.JPG then we override the date_taken - (year, month, day) = [None] * 3 - year_month_day_match = re.search('(\d{4})-(\d{2})-(\d{2})', file_name) - if(year_month_day_match is not None): - (year, month, day) = year_month_day_match.groups() - - # check if the file system path indicated a date and if so we - # override the metadata value - if(year is not None and month is not None and day is not None): - date_taken = time.strptime( - '{}-{}-{}'.format(year, month, day), - '%Y-%m-%d' - ) - - os.utime(file, (time.time(), time.mktime(date_taken))) - - def set_date_from_path_video(self, video): - """Set the modification time on the file based on the file path. - - Noop if the path doesn't match the format YYYY-MM/DD-IMG_0001.JPG. - - :param elodie.media.video.Video video: An instance of Video. - """ - date_taken = None - - video_file_path = video.get_file_path() - - # Initialize date taken to what's returned from the metadata function. - # If the folder and file name follow a time format of - # YYYY-MM/DD-IMG_0001.JPG then we override the date_taken - (year, month, day) = [None] * 3 - directory = os.path.dirname(video_file_path) - # If the directory matches we get back a match with - # groups() = (year, month) - year_month_match = re.search('(\d{4})-(\d{2})', directory) - if(year_month_match is not None): - (year, month) = year_month_match.groups() - day_match = re.search( - '^(\d{2})', - os.path.basename(video.get_file_path()) - ) - if(day_match is not None): - day = day_match.group(1) - - # check if the file system path indicated a date and if so we - # override the metadata value - if(year is not None and month is not None): - if(day is not None): - date_taken = time.strptime( - '{}-{}-{}'.format(year, month, day), - '%Y-%m-%d' - ) - else: - date_taken = time.strptime( - '{}-{}'.format(year, month), - '%Y-%m' - ) - - os.utime(video_file_path, (time.time(), time.mktime(date_taken))) From 54f3fac155a1e44bbfc4a04892ebd6f33c93d187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Dr=C3=A4s?= Date: Sun, 9 Oct 2016 23:12:16 +0200 Subject: [PATCH 3/6] gh-139 Target Directory on NAS --- elodie/filesystem.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/elodie/filesystem.py b/elodie/filesystem.py index 79ca367..b1c341e 100644 --- a/elodie/filesystem.py +++ b/elodie/filesystem.py @@ -14,6 +14,11 @@ import time from elodie import geolocation from elodie import constants from elodie.localstorage import Db +from elodie.media.media import Media +from elodie.media.text import Text +from elodie.media.audio import Audio +from elodie.media.photo import Photo +from elodie.media.video import Video class FileSystem(object): @@ -213,13 +218,38 @@ class FileSystem(object): shutil.move(_file, dest_path) os.utime(dest_path, (stat.st_atime, stat.st_mtime)) else: - shutil.copy2(_file, dest_path) + shutil.copy(_file, dest_path) + self.set_date_from_filename(dest_path) db.add_hash(checksum, dest_path) db.update_hash_db() return dest_path + def set_date_from_filename(self, file): + """ Set the modification time on the file base on the file name. + """ + + date_taken = None + file_name = os.path.basename(file) + # Initialize date taken to what's returned from the metadata function. + # If the folder and file name follow a time format of + # YYYY-MM/DD-IMG_0001.JPG then we override the date_taken + (year, month, day) = [None] * 3 + year_month_day_match = re.search('(\d{4})-(\d{2})-(\d{2})', file_name) + if(year_month_day_match is not None): + (year, month, day) = year_month_day_match.groups() + + # check if the file system path indicated a date and if so we + # override the metadata value + if(year is not None and month is not None and day is not None): + date_taken = time.strptime( + '{}-{}-{}'.format(year, month, day), + '%Y-%m-%d' + ) + + os.utime(file, (time.time(), time.mktime(date_taken))) + def set_date_from_path_video(self, video): """Set the modification time on the file based on the file path. From 56847511b9e1c0176f2ef2161004b584fa6cb477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Dr=C3=A4s?= Date: Sat, 15 Oct 2016 22:00:51 +0200 Subject: [PATCH 4/6] gh-139 Add files via upload added hours, minutes and seconds --- elodie/filesystem.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/elodie/filesystem.py b/elodie/filesystem.py index b1c341e..69c306f 100644 --- a/elodie/filesystem.py +++ b/elodie/filesystem.py @@ -235,17 +235,17 @@ class FileSystem(object): # Initialize date taken to what's returned from the metadata function. # If the folder and file name follow a time format of # YYYY-MM/DD-IMG_0001.JPG then we override the date_taken - (year, month, day) = [None] * 3 - year_month_day_match = re.search('(\d{4})-(\d{2})-(\d{2})', file_name) + (year, month, day, hour, minute, second) = [None] * 6 + year_month_day_match = re.search('(\d{4})-(\d{2})-(\d{2})_(\d{2})-(\d{2})-(\d{2})', file_name) if(year_month_day_match is not None): - (year, month, day) = year_month_day_match.groups() + (year, month, day, hour, minute, second) = year_month_day_match.groups() # check if the file system path indicated a date and if so we # override the metadata value - if(year is not None and month is not None and day is not None): + if(year is not None and month is not None and day is not None and hour is not None and minute is not None and second is not None): date_taken = time.strptime( - '{}-{}-{}'.format(year, month, day), - '%Y-%m-%d' + '{}-{}-{} {}:{}:{}'.format(year, month, day, hour, minute, second), + '%Y-%m-%d %H:%M:%S' ) os.utime(file, (time.time(), time.mktime(date_taken))) From f00d8b8744104c84e3c5fae82be51b13f25db840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Dr=C3=A4s?= Date: Fri, 21 Oct 2016 22:01:31 +0200 Subject: [PATCH 5/6] gh-139 added change requests --- elodie/filesystem.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/elodie/filesystem.py b/elodie/filesystem.py index 69c306f..9bd590a 100644 --- a/elodie/filesystem.py +++ b/elodie/filesystem.py @@ -15,10 +15,6 @@ from elodie import geolocation from elodie import constants from elodie.localstorage import Db from elodie.media.media import Media -from elodie.media.text import Text -from elodie.media.audio import Audio -from elodie.media.photo import Photo -from elodie.media.video import Video class FileSystem(object): @@ -218,6 +214,8 @@ class FileSystem(object): shutil.move(_file, dest_path) os.utime(dest_path, (stat.st_atime, stat.st_mtime)) else: + # 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 shutil.copy(_file, dest_path) self.set_date_from_filename(dest_path) @@ -234,7 +232,7 @@ class FileSystem(object): file_name = os.path.basename(file) # Initialize date taken to what's returned from the metadata function. # If the folder and file name follow a time format of - # YYYY-MM/DD-IMG_0001.JPG then we override the date_taken + # YYYY-MM-DD_HH-MM-SS-IMG_0001.JPG then we override the date_taken (year, month, day, hour, minute, second) = [None] * 6 year_month_day_match = re.search('(\d{4})-(\d{2})-(\d{2})_(\d{2})-(\d{2})-(\d{2})', file_name) if(year_month_day_match is not None): From ea34ccbf6c1229c1d18d6c8b5f384c892e4f5447 Mon Sep 17 00:00:00 2001 From: Jaisen Mathai Date: Fri, 21 Oct 2016 17:09:26 -0700 Subject: [PATCH 6/6] gh-139 Update logic to set file access/modify time --- elodie.py | 3 -- elodie/filesystem.py | 89 +++++++++------------------------ elodie/tests/filesystem_test.py | 58 +++++++++++++++++++++ 3 files changed, 83 insertions(+), 67 deletions(-) diff --git a/elodie.py b/elodie.py index 50ba8d2..e0fd37b 100755 --- a/elodie.py +++ b/elodie.py @@ -48,9 +48,6 @@ def import_file(_file, destination, album_from_folder, trash, allow_duplicates): print('{"source":"%s", "error_msg":"Not a supported file"}' % _file) return - if media.__name__ == 'Video': - FILESYSTEM.set_date_from_path_video(media) - if album_from_folder: media.set_album_from_folder() diff --git a/elodie/filesystem.py b/elodie/filesystem.py index 9bd590a..0bb0aa8 100644 --- a/elodie/filesystem.py +++ b/elodie/filesystem.py @@ -14,7 +14,6 @@ import time from elodie import geolocation from elodie import constants from elodie.localstorage import Db -from elodie.media.media import Media class FileSystem(object): @@ -214,80 +213,42 @@ class FileSystem(object): shutil.move(_file, dest_path) os.utime(dest_path, (stat.st_atime, stat.st_mtime)) else: - # 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 using copy and manual + # set_date_from_filename gets the job done shutil.copy(_file, dest_path) - self.set_date_from_filename(dest_path) + self.set_utime(media) db.add_hash(checksum, dest_path) db.update_hash_db() return dest_path - def set_date_from_filename(self, file): + def set_utime(self, media): """ Set the modification time on the file base on the file name. """ - - date_taken = None - file_name = os.path.basename(file) + # Initialize date taken to what's returned from the metadata function. # If the folder and file name follow a time format of # YYYY-MM-DD_HH-MM-SS-IMG_0001.JPG then we override the date_taken - (year, month, day, hour, minute, second) = [None] * 6 - year_month_day_match = re.search('(\d{4})-(\d{2})-(\d{2})_(\d{2})-(\d{2})-(\d{2})', file_name) - if(year_month_day_match is not None): - (year, month, day, hour, minute, second) = year_month_day_match.groups() - - # check if the file system path indicated a date and if so we - # override the metadata value - if(year is not None and month is not None and day is not None and hour is not None and minute is not None and second is not None): - date_taken = time.strptime( - '{}-{}-{} {}:{}:{}'.format(year, month, day, hour, minute, second), - '%Y-%m-%d %H:%M:%S' - ) - - os.utime(file, (time.time(), time.mktime(date_taken))) - - def set_date_from_path_video(self, video): - """Set the modification time on the file based on the file path. - - Noop if the path doesn't match the format YYYY-MM/DD-IMG_0001.JPG. - - :param elodie.media.video.Video video: An instance of Video. - """ - date_taken = None - - video_file_path = video.get_file_path() - - # Initialize date taken to what's returned from the metadata function. - # If the folder and file name follow a time format of - # YYYY-MM/DD-IMG_0001.JPG then we override the date_taken - (year, month, day) = [None] * 3 - directory = os.path.dirname(video_file_path) - # If the directory matches we get back a match with - # groups() = (year, month) - year_month_match = re.search('(\d{4})-(\d{2})', directory) - if(year_month_match is not None): - (year, month) = year_month_match.groups() - day_match = re.search( - '^(\d{2})', - os.path.basename(video.get_file_path()) + file_path = media.get_file_path() + metadata = media.get_metadata() + date_taken = metadata['date_taken'] + base_name = metadata['base_name'] + year_month_day_match = re.search( + '^(\d{4})-(\d{2})-(\d{2})_(\d{2})-(\d{2})-(\d{2})', + base_name ) - if(day_match is not None): - day = day_match.group(1) + if(year_month_day_match is not None): + (year, month, day, hour, minute, second) = year_month_day_match.groups() # noqa + date_taken = time.strptime( + '{}-{}-{} {}:{}:{}'.format(year, month, day, hour, minute, second), # noqa + '%Y-%m-%d %H:%M:%S' + ) - # check if the file system path indicated a date and if so we - # override the metadata value - if(year is not None and month is not None): - if(day is not None): - date_taken = time.strptime( - '{}-{}-{}'.format(year, month, day), - '%Y-%m-%d' - ) - else: - date_taken = time.strptime( - '{}-{}'.format(year, month), - '%Y-%m' - ) - - os.utime(video_file_path, (time.time(), time.mktime(date_taken))) + os.utime(file_path, (time.time(), time.mktime(date_taken))) + else: + # We don't make any assumptions about time zones and + # assume local time zone. + date_taken_in_seconds = time.mktime(date_taken) + os.utime(file_path, (time.time(), (date_taken_in_seconds))) diff --git a/elodie/tests/filesystem_test.py b/elodie/tests/filesystem_test.py index dfb13d1..25d7c7a 100644 --- a/elodie/tests/filesystem_test.py +++ b/elodie/tests/filesystem_test.py @@ -364,3 +364,61 @@ def test_process_video_with_album_then_title(): assert origin_checksum is not None, origin_checksum assert origin_checksum != destination_checksum, destination_checksum assert helper.path_tz_fix(os.path.join('2015-01-Jan','test_album','2015-01-19_12-45-11-movie-test_title.mov')) in destination, destination + +def test_set_utime_with_exif_date(): + filesystem = FileSystem() + temporary_folder, folder = helper.create_working_folder() + + origin = os.path.join(folder,'photo.jpg') + shutil.copyfile(helper.get_file('plain.jpg'), origin) + + media_initial = Photo(origin) + metadata_initial = media_initial.get_metadata() + + initial_stat = os.stat(origin) + initial_time = int(min(initial_stat.st_mtime, initial_stat.st_ctime)) + initial_checksum = helper.checksum(origin) + + assert initial_time != time.mktime(metadata_initial['date_taken']) + + filesystem.set_utime(media_initial) + final_stat = os.stat(origin) + final_checksum = helper.checksum(origin) + + media_final = Photo(origin) + metadata_final = media_final.get_metadata() + + shutil.rmtree(folder) + + assert initial_stat.st_mtime != final_stat.st_mtime + assert final_stat.st_mtime == time.mktime(metadata_final['date_taken']) + assert initial_checksum == final_checksum + +def test_set_utime_without_exif_date(): + filesystem = FileSystem() + temporary_folder, folder = helper.create_working_folder() + + origin = os.path.join(folder,'photo.jpg') + shutil.copyfile(helper.get_file('no-exif.jpg'), origin) + + media_initial = Photo(origin) + metadata_initial = media_initial.get_metadata() + + initial_stat = os.stat(origin) + initial_time = int(min(initial_stat.st_mtime, initial_stat.st_ctime)) + initial_checksum = helper.checksum(origin) + + assert initial_time == time.mktime(metadata_initial['date_taken']) + + filesystem.set_utime(media_initial) + final_stat = os.stat(origin) + final_checksum = helper.checksum(origin) + + media_final = Photo(origin) + metadata_final = media_final.get_metadata() + + shutil.rmtree(folder) + + assert initial_time == final_stat.st_mtime + assert final_stat.st_mtime == time.mktime(metadata_final['date_taken']), (final_stat.st_mtime, time.mktime(metadata_final['date_taken'])) + assert initial_checksum == final_checksum