""" Author: Jaisen Mathai Media package that handles all video operations """ # load modules from datetime import datetime from distutils.spawn import find_executable from elodie import constants from fractions import Fraction from sys import argv import mimetypes import os import pyexiv2 import re import subprocess import time """ Media class for general video operations """ class Media(object): # class / static variable accessible through get_valid_extensions() __name__ = 'Media' """ @param, source, string, The fully qualified path to the video file """ def __init__(self, source=None): self.source = source self.exif_map = { 'date_taken': ['Exif.Photo.DateTimeOriginal', 'Exif.Image.DateTime'], #, 'EXIF FileDateTime'], 'latitude': 'Exif.GPSInfo.GPSLatitude', 'latitude_ref': 'Exif.GPSInfo.GPSLatitudeRef', 'longitude': 'Exif.GPSInfo.GPSLongitude', 'longitude_ref': 'Exif.GPSInfo.GPSLongitudeRef', } self.exiftool_attributes = None self.metadata = None """ Get album from EXIF @returns, None or string """ def get_album(self): if(not self.is_valid()): return None exiftool_attributes = self.get_exiftool_attributes() if(exiftool_attributes is None or 'album' not in exiftool_attributes): return None return exiftool_attributes['album'] """ Get path to executable exiftool binary. We wrap this since we call it in a few places and we do a fallback. @returns, None or string """ def get_exiftool(self): exiftool = find_executable('exiftool') # If exiftool wasn't found we try to brute force the homebrew location if(exiftool is None): exiftool = '/usr/local/bin/exiftool' if(not os.path.isfile(exiftool) or not os.access(exiftool, os.X_OK)): return None return exiftool """ Get the full path to the video. @returns string """ def get_file_path(self): return self.source """ Define is_valid to always return false. This should be overridden in a child class. """ def is_valid(self): return False """ Read EXIF from a photo file. We store the result in a member variable so we can call get_exif() often without performance degredation @returns, list or none for a non-photo file """ def get_exif(self): if(not self.is_valid()): return None if(self.exif is not None): return self.exif source = self.source self.exif = pyexiv2.ImageMetadata(source) self.exif.read() return self.exif def get_exiftool_attributes(self): if(self.exiftool_attributes is not None): return self.exiftool_attributes exiftool = self.get_exiftool() if(exiftool is None): return False source = self.source process_output = subprocess.Popen(['%s "%s"' % (exiftool, source)], stdout=subprocess.PIPE, shell=True) output = process_output.stdout.read() # Get album from exiftool output album = None album_regex = re.search('Album +: +(.+)', output) if(album_regex is not None): album = album_regex.group(1) # Get title from exiftool output title = None for key in ['Displayname', 'Headline', 'Title', 'ImageDescription']: title_regex = re.search('%s +: +(.+)' % key, output) if(title_regex is not None): title_return = title_regex.group(1).strip() if(len(title_return) > 0): title = title_return break; self.exiftool_attributes = { 'album': album, 'title': title } return self.exiftool_attributes """ Get the file extension as a lowercased string. @returns, string or None for a non-video """ def get_extension(self): if(not self.is_valid()): return None source = self.source return os.path.splitext(source)[1][1:].lower() """ Get a dictionary of metadata for a photo. All keys will be present and have a value of None if not obtained. @returns, dictionary or None for non-photo files """ def get_metadata(self, update_cache=False): if(not self.is_valid()): return None if(self.metadata is not None and update_cache == False): return self.metadata source = self.source self.metadata = { 'date_taken': self.get_date_taken(), 'latitude': self.get_coordinate('latitude'), 'longitude': self.get_coordinate('longitude'), 'album': self.get_album(), 'title': self.get_title(), 'mime_type': self.get_mimetype(), 'base_name': os.path.splitext(os.path.basename(source))[0], 'extension': self.get_extension(), 'directory_path': os.path.dirname(source) } return self.metadata """ Get the mimetype of the file. @returns, string or None for a non-video """ def get_mimetype(self): if(not self.is_valid()): return None source = self.source mimetype = mimetypes.guess_type(source) if(mimetype == None): return None return mimetype[0] """ Get the title for a photo of video @returns, string or None if no title is set or not a valid media type """ def get_title(self): if(not self.is_valid()): return None exiftool_attributes = self.get_exiftool_attributes() if(exiftool_attributes is None or 'title' not in exiftool_attributes): return None return exiftool_attributes['title'] """ Set album for a photo @param, name, string, Name of album @returns, boolean """ def set_album(self, name): if(name is None): return False exiftool = self.get_exiftool() if(exiftool is None): return False source = self.source stat = os.stat(source) exiftool_config = constants.exiftool_config if(constants.debug == True): print '%s -config "%s" -xmp-elodie:Album="%s" "%s"' % (exiftool, exiftool_config, name, source) process_output = subprocess.Popen(['%s -config "%s" -xmp-elodie:Album="%s" "%s"' % (exiftool, exiftool_config, name, source)], stdout=subprocess.PIPE, shell=True) streamdata = process_output.communicate()[0] if(process_output.returncode != 0): return False os.utime(source, (stat.st_atime, stat.st_mtime)) exiftool_backup_file = '%s%s' % (source, '_original') if(os.path.isfile(exiftool_backup_file) is True): os.remove(exiftool_backup_file) self.set_metadata(album=name) return True def set_album_from_folder(self): metadata = self.get_metadata() print 'huh/' # If this file has an album already set we do not overwrite EXIF if(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) return True """ Specifically update the basename attribute in the metadata dictionary 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, string, new_basename, New basename of file (with the old title removed """ def set_metadata_basename(self, new_basename): self.get_metadata() self.metadata['base_name'] = new_basename """ Method to manually update attributes in metadata. @params, named paramaters """ def set_metadata(self, **kwargs): metadata = self.get_metadata() for key in kwargs: if(key in metadata): self.metadata[key] = kwargs[key] @classmethod def get_class_by_file(Media, _file, classes): extension = os.path.splitext(_file)[1][1:].lower() for i in classes: if(extension in i.extensions): return i(_file) return None