ordigi/elodie/filesystem.py

201 lines
7.1 KiB
Python

"""
Author: Jaisen Mathai <jaisen@jmathai.com>
Video package that handles all video operations
"""
import os
import re
import shutil
import time
from elodie import geolocation
from elodie import constants
from elodie.localstorage import Db
"""
General file system methods
"""
class FileSystem:
"""
Create a directory if it does not already exist..
@param, directory_name, string, A fully qualified path of the directory to create.
"""
def create_directory(self, directory_path):
if not os.path.exists(directory_path):
os.makedirs(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, directory_name, string, A fully qualified path of the directory to delete.
"""
def delete_directory_if_empty(self, directory_path):
try:
os.rmdir(directory_path)
except OSError:
pass
"""
Recursively get all files which match a path and extension.
@param, path, string, Path to start recursive file listing
@param, extensions, tuple, File extensions to include (whitelist)
"""
def get_all_files(self, path, extensions=None):
files = []
for dirname, dirnames, filenames in os.walk(path):
# print path to all filenames.
for filename in filenames:
if(extensions == None or filename.lower().endswith(extensions)):
files.append('%s/%s' % (dirname, filename))
return files
"""
Get the current working directory
@returns, string
"""
def get_current_directory(self):
return os.getcwd()
"""
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, Photo|Video, A Photo or Video instance
@returns, string or None for non-photo or non-videos
"""
def get_file_name(self, media):
if(not media.is_valid()):
return None
metadata = media.get_metadata()
if(metadata == 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()
"""
Get date based folder name.
@param, time_obj, time, Time object to be used to determine folder name.
@returns, string
"""
def get_folder_name_by_date(self, time_obj):
return time.strftime('%Y-%m-%b', time_obj)
"""
Get folder path by various parameters.
@param, time_obj, time, Time object to be used to determine folder name.
@returns, string
"""
def get_folder_path(self, metadata):
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 '/'.join(path)
def process_file(self, _file, destination, media, **kwargs):
move = False
if('move' in kwargs):
move = kwargs['move']
allowDuplicate = False
if('allowDuplicate' in kwargs):
allowDuplicate = kwargs['allowDuplicate']
metadata = media.get_metadata()
directory_name = self.get_folder_path(metadata)
dest_directory = '%s/%s' % (destination, directory_name)
file_name = self.get_file_name(media)
dest_path = '%s/%s' % (dest_directory, file_name)
db = Db()
checksum = db.checksum(_file)
if(checksum == None):
if(constants.debug == 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(allowDuplicate == False and db.check_hash(checksum) == True):
if(constants.debug == True):
print '%s already exists at %s. Skipping...' % (_file, db.get_hash(checksum))
return
self.create_directory(dest_directory)
if(move == True):
stat = os.stat(_file)
shutil.move(_file, dest_path)
os.utime(dest_path, (stat.st_atime, stat.st_mtime))
else:
shutil.copy2(_file, dest_path)
db.add_hash(checksum, dest_path)
db.update_hash_db()
return dest_path
"""
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.
"""
def set_date_from_path_video(self, 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)))