Refactoring media class
This commit is contained in:
parent
6b2d2b31c3
commit
cb8a4cd24e
20
elodie.py
20
elodie.py
|
@ -155,8 +155,8 @@ def _import(destination, source, file, album_from_folder, trash,
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@click.command('generate-db')
|
@click.command('generate-db')
|
||||||
@click.option('--source', type=click.Path(file_okay=False),
|
@click.option('--path', type=click.Path(file_okay=False),
|
||||||
required=True, help='Source of your photo library.')
|
required=True, help='Path of your photo library.')
|
||||||
@click.option('--debug', default=False, is_flag=True,
|
@click.option('--debug', default=False, is_flag=True,
|
||||||
help='Override the value in constants.py with True.')
|
help='Override the value in constants.py with True.')
|
||||||
def _generate_db(path, debug):
|
def _generate_db(path, debug):
|
||||||
|
@ -164,17 +164,17 @@ def _generate_db(path, debug):
|
||||||
"""
|
"""
|
||||||
constants.debug = debug
|
constants.debug = debug
|
||||||
result = Result()
|
result = Result()
|
||||||
source = os.path.abspath(os.path.expanduser(source))
|
path = os.path.abspath(os.path.expanduser(path))
|
||||||
|
|
||||||
if not os.path.isdir(source):
|
if not os.path.isdir(path):
|
||||||
log.error('Source is not a valid directory %s' % source)
|
log.error('path is not a valid directory %s' % path)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
db = Db(path)
|
db = Db(path)
|
||||||
db.backup_hash_db()
|
db.backup_hash_db()
|
||||||
db.reset_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))
|
result.append((current_file, True))
|
||||||
db.add_hash(db.checksum(current_file), current_file)
|
db.add_hash(db.checksum(current_file), current_file)
|
||||||
log.progress()
|
log.progress()
|
||||||
|
@ -184,9 +184,11 @@ def _generate_db(path, debug):
|
||||||
result.write()
|
result.write()
|
||||||
|
|
||||||
@click.command('verify')
|
@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,
|
@click.option('--debug', default=False, is_flag=True,
|
||||||
help='Override the value in constants.py with True.')
|
help='Override the value in constants.py with True.')
|
||||||
def _verify(debug):
|
def _verify(path, debug):
|
||||||
constants.debug = debug
|
constants.debug = debug
|
||||||
result = Result()
|
result = Result()
|
||||||
db = Db(path)
|
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 \
|
if location_coords and 'latitude' in location_coords and \
|
||||||
'longitude' in location_coords:
|
'longitude' in location_coords:
|
||||||
location_status = media.set_location(location_coords[
|
location_status = media.set_location(location_coords[
|
||||||
'latitude'], location_coords['longitude'])
|
'latitude'], location_coords['longitude'], file_path)
|
||||||
if not location_status:
|
if not location_status:
|
||||||
log.error('Failed to update location')
|
log.error('Failed to update location')
|
||||||
log.all(('{"source":"%s",' % file_path,
|
log.all(('{"source":"%s",' % file_path,
|
||||||
|
@ -307,7 +309,7 @@ def _update(album, location, time, title, paths, debug):
|
||||||
update_time(media, current_file, time)
|
update_time(media, current_file, time)
|
||||||
updated = True
|
updated = True
|
||||||
if album:
|
if album:
|
||||||
media.set_album(album)
|
media.set_album(album, current_file)
|
||||||
updated = True
|
updated = True
|
||||||
|
|
||||||
import ipdb; ipdb.set_trace()
|
import ipdb; ipdb.set_trace()
|
||||||
|
|
|
@ -17,7 +17,7 @@ from elodie.config import load_config
|
||||||
from elodie import constants
|
from elodie import constants
|
||||||
|
|
||||||
from elodie.localstorage import Db
|
from elodie.localstorage import Db
|
||||||
from elodie.media import base
|
from elodie.media import media
|
||||||
from elodie.plugins.plugins import Plugins
|
from elodie.plugins.plugins import Plugins
|
||||||
|
|
||||||
class FileSystem(object):
|
class FileSystem(object):
|
||||||
|
@ -94,7 +94,7 @@ class FileSystem(object):
|
||||||
# If extensions is None then we get all supported extensions
|
# If extensions is None then we get all supported extensions
|
||||||
if not extensions:
|
if not extensions:
|
||||||
extensions = set()
|
extensions = set()
|
||||||
subclasses = base.get_all_subclasses()
|
subclasses = media.get_all_subclasses()
|
||||||
for cls in subclasses:
|
for cls in subclasses:
|
||||||
extensions.update(cls.extensions)
|
extensions.update(cls.extensions)
|
||||||
|
|
||||||
|
@ -679,7 +679,9 @@ class FileSystem(object):
|
||||||
if album_from_folder:
|
if album_from_folder:
|
||||||
media.set_album_from_folder(dest_path)
|
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()
|
db.update_hash_db()
|
||||||
|
|
||||||
# Run `after()` for every loaded plugin and if any of them raise an exception
|
# Run `after()` for every loaded plugin and if any of them raise an exception
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
"""
|
"""
|
||||||
The audio module contains classes specifically for dealing with audio files.
|
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.
|
class.
|
||||||
|
|
||||||
.. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com>
|
.. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .video import Video
|
from .media import Media
|
||||||
|
|
||||||
|
|
||||||
class Audio(Video):
|
class Audio(Media):
|
||||||
|
|
||||||
"""An audio object.
|
"""An audio object.
|
||||||
|
|
||||||
|
@ -22,4 +22,7 @@ class Audio(Video):
|
||||||
extensions = ('m4a',)
|
extensions = ('m4a',)
|
||||||
|
|
||||||
def __init__(self, source=None):
|
def __init__(self, source=None):
|
||||||
super(Audio, self).__init__(source)
|
super().__init__(source)
|
||||||
|
|
||||||
|
def is_valid(self):
|
||||||
|
return super().is_valid()
|
||||||
|
|
|
@ -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 <jaisen@jmathai.com>
|
|
||||||
"""
|
|
||||||
|
|
||||||
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
|
|
|
@ -1,14 +1,14 @@
|
||||||
"""
|
"""
|
||||||
The media module provides a base :class:`Media` class for media objects that
|
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
|
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
|
by all the media types. Its sub-classes (:class:`~elodie.media.Audio`,
|
||||||
sub-classes (:class:`~elodie.media.audio.Audio`,
|
:class:`~elodie.media.Photo`, and :class:`~elodie.media.Video`)
|
||||||
:class:`~elodie.media.photo.Photo`, and :class:`~elodie.media.video.Video`)
|
|
||||||
are used to represent the actual files.
|
are used to represent the actual files.
|
||||||
|
|
||||||
.. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com>
|
.. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
@ -17,11 +17,17 @@ from elodie import log
|
||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
import re
|
import re
|
||||||
from elodie.external.pyexiftool import ExifTool
|
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.
|
:param str source: The fully qualified path to the video file.
|
||||||
"""
|
"""
|
||||||
|
@ -33,23 +39,221 @@ class Media(Base):
|
||||||
'longitude': 'longitude_ref'
|
'longitude': 'longitude_ref'
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, source=None):
|
PHOTO = ('arw', 'cr2', 'dng', 'gif', 'heic', 'jpeg', 'jpg', 'nef', 'png', 'rw2')
|
||||||
super(Media, self).__init__(source)
|
AUDIO = ('m4a',)
|
||||||
self.date_original = ['EXIF:DateTimeOriginal']
|
VIDEO = ('avi', 'm4v', 'mov', 'mp4', 'mpg', 'mpeg', '3gp', 'mts')
|
||||||
self.date_created = ['EXIF:CreateDate', 'QuickTime:CreateDate']
|
|
||||||
|
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.date_modified = ['File:FileModifyDate', 'QuickTime:ModifyDate']
|
||||||
self.camera_make_keys = ['EXIF:Make', 'QuickTime:Make']
|
self.camera_make_keys = ['EXIF:Make', 'QuickTime:Make']
|
||||||
self.camera_model_keys = ['EXIF:Model', 'QuickTime:Model']
|
self.camera_model_keys = ['EXIF:Model', 'QuickTime:Model']
|
||||||
self.album_keys = ['XMP-xmpDM:Album', 'XMP:Album']
|
self.album_keys = ['XMP-xmpDM:Album', 'XMP:Album']
|
||||||
self.title_key = 'XMP:Title'
|
self.title_keys = ['XMP:Title', 'XMP:DisplayName']
|
||||||
self.latitude_keys = ['EXIF:GPSLatitude']
|
self.latitude_keys = [
|
||||||
self.longitude_keys = ['EXIF:GPSLongitude']
|
'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.latitude_ref_key = 'EXIF:GPSLatitudeRef'
|
||||||
self.longitude_ref_key = 'EXIF:GPSLongitudeRef'
|
self.longitude_ref_key = 'EXIF:GPSLongitudeRef'
|
||||||
self.original_name_key = 'XMP:OriginalFileName'
|
self.original_name_key = 'XMP:OriginalFileName'
|
||||||
self.set_gps_ref = True
|
self.set_gps_ref = True
|
||||||
|
self.metadata = None
|
||||||
self.exif_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):
|
def get_album(self):
|
||||||
"""Get album from EXIF
|
"""Get album from EXIF
|
||||||
|
|
||||||
|
@ -68,6 +272,7 @@ class Media(Base):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_coordinate(self, type='latitude'):
|
def get_coordinate(self, type='latitude'):
|
||||||
"""Get latitude or longitude of media from EXIF
|
"""Get latitude or longitude of media from EXIF
|
||||||
|
|
||||||
|
@ -114,6 +319,7 @@ class Media(Base):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_exiftool_attributes(self):
|
def get_exiftool_attributes(self):
|
||||||
"""Get attributes for the media object from exiftool.
|
"""Get attributes for the media object from exiftool.
|
||||||
|
|
||||||
|
@ -185,6 +391,7 @@ class Media(Base):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_camera_model(self):
|
def get_camera_model(self):
|
||||||
"""Get the camera make stored in EXIF.
|
"""Get the camera make stored in EXIF.
|
||||||
|
|
||||||
|
@ -204,6 +411,7 @@ class Media(Base):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_original_name(self):
|
def get_original_name(self):
|
||||||
"""Get the original name stored in EXIF.
|
"""Get the original name stored in EXIF.
|
||||||
|
|
||||||
|
@ -222,6 +430,7 @@ class Media(Base):
|
||||||
|
|
||||||
return exiftool_attributes[self.original_name_key]
|
return exiftool_attributes[self.original_name_key]
|
||||||
|
|
||||||
|
|
||||||
def get_title(self):
|
def get_title(self):
|
||||||
"""Get the title for a photo of video
|
"""Get the title for a photo of video
|
||||||
|
|
||||||
|
@ -235,17 +444,19 @@ class Media(Base):
|
||||||
if exiftool_attributes is None:
|
if exiftool_attributes is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if(self.title_key not in exiftool_attributes):
|
for title_key in self.title_keys:
|
||||||
|
if title_key in exiftool_attributes:
|
||||||
|
return exiftool_attributes[title_key]
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return exiftool_attributes[self.title_key]
|
|
||||||
|
|
||||||
def reset_cache(self):
|
def reset_cache(self):
|
||||||
"""Resets any internal cache
|
"""Resets any internal cache
|
||||||
"""
|
"""
|
||||||
self.exiftool_attributes = None
|
self.exiftool_attributes = None
|
||||||
self.exif_metadata = None
|
self.exif_metadata = None
|
||||||
super(Media, self).reset_cache()
|
|
||||||
|
|
||||||
def set_album(self, name, path):
|
def set_album(self, name, path):
|
||||||
"""Set album EXIF tag if not already set.
|
"""Set album EXIF tag if not already set.
|
||||||
|
@ -255,12 +466,16 @@ class Media(Base):
|
||||||
if self.get_album() is not None:
|
if self.get_album() is not None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
tags = {self.album_keys[0]: name}
|
tags = {}
|
||||||
status = ExifTool().set_tags(tags, path)
|
for key in self.album_keys:
|
||||||
|
tags[key] = name
|
||||||
|
status = self.__set_tags(tags, path)
|
||||||
self.reset_cache()
|
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.
|
"""Set the date/time a photo was taken.
|
||||||
|
|
||||||
:param datetime time: datetime object of when the 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:
|
for key in self.date_original:
|
||||||
tags[key] = formatted_time
|
tags[key] = formatted_time
|
||||||
|
|
||||||
status = self.__set_tags(tags)
|
status = self.__set_tags(tags, path)
|
||||||
if status == False:
|
if status == False:
|
||||||
# exif attribute date_original d'ont exist
|
# exif attribute date_original d'ont exist
|
||||||
for key in self.date_created:
|
for key in self.date_created:
|
||||||
tags[key] = formatted_time
|
tags[key] = formatted_time
|
||||||
|
|
||||||
status = self.__set_tags(tags)
|
status = self.__set_tags(tags, path)
|
||||||
self.reset_cache()
|
self.reset_cache()
|
||||||
return status
|
return status
|
||||||
|
|
||||||
def set_location(self, latitude, longitude):
|
|
||||||
|
def set_location(self, latitude, longitude, path):
|
||||||
if(not self.is_valid()):
|
if(not self.is_valid()):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# The lat/lon _keys array has an order of precedence.
|
# The lat/lon _keys array has an order of precedence.
|
||||||
# The first key is writable and we will give the writable
|
# The first key is writable and we will give the writable
|
||||||
# key precence when reading.
|
# key precence when reading.
|
||||||
tags = {
|
# TODO check
|
||||||
self.latitude_keys[0]: latitude,
|
# tags = {
|
||||||
self.longitude_keys[0]: longitude,
|
# 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
|
# If self.set_gps_ref == True then it means we are writing an EXIF
|
||||||
# GPS tag which requires us to set the reference key.
|
# GPS tag which requires us to set the reference key.
|
||||||
# That's because the lat/lon are absolute values.
|
# That's because the lat/lon are absolute values.
|
||||||
|
# TODO set_gps_ref = False for Video ?
|
||||||
if self.set_gps_ref:
|
if self.set_gps_ref:
|
||||||
if latitude < 0:
|
if latitude < 0:
|
||||||
tags[self.latitude_ref_key] = 'S'
|
tags[self.latitude_ref_key] = 'S'
|
||||||
|
@ -306,12 +529,13 @@ class Media(Base):
|
||||||
if longitude < 0:
|
if longitude < 0:
|
||||||
tags[self.longitude_ref_key] = 'W'
|
tags[self.longitude_ref_key] = 'W'
|
||||||
|
|
||||||
status = self.__set_tags(tags)
|
status = self.__set_tags(tags, path)
|
||||||
self.reset_cache()
|
self.reset_cache()
|
||||||
|
|
||||||
return status
|
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.
|
"""Sets the original name EXIF tag if not already set.
|
||||||
|
|
||||||
:returns: True, False, None
|
:returns: True, False, None
|
||||||
|
@ -320,14 +544,17 @@ class Media(Base):
|
||||||
if self.get_original_name() is not None:
|
if self.get_original_name() is not None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
name = os.path.basename(path)
|
if name == None:
|
||||||
|
name = os.path.basename(self.source)
|
||||||
|
|
||||||
tags = {self.original_name_key: name}
|
tags = {self.original_name_key: name}
|
||||||
status = ExifTool().set_tags(tags, path)
|
status = self.__set_tags(tags, path)
|
||||||
self.reset_cache()
|
self.reset_cache()
|
||||||
return status != ''
|
|
||||||
|
|
||||||
def set_title(self, title):
|
return status
|
||||||
|
|
||||||
|
|
||||||
|
def set_title(self, title, path):
|
||||||
"""Set title for a photo.
|
"""Set title for a photo.
|
||||||
|
|
||||||
:param str title: Title of the photo.
|
:param str title: Title of the photo.
|
||||||
|
@ -339,23 +566,43 @@ class Media(Base):
|
||||||
if(title is None):
|
if(title is None):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
tags = {self.title_key: title}
|
tags = {}
|
||||||
status = self.__set_tags(tags)
|
for key in self.title_keys:
|
||||||
|
tags[key] = title
|
||||||
|
status = self.__set_tags(tags, path)
|
||||||
self.reset_cache()
|
self.reset_cache()
|
||||||
|
|
||||||
return status
|
return status
|
||||||
|
|
||||||
def __set_tags(self, tags):
|
|
||||||
|
def __set_tags(self, tags, path):
|
||||||
if(not self.is_valid()):
|
if(not self.is_valid()):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
source = self.source
|
|
||||||
|
|
||||||
status = ''
|
status = ''
|
||||||
status = ExifTool().set_tags(tags,source)
|
status = ExifTool().set_tags(tags, path)
|
||||||
if status.decode().find('unchanged') != -1 or status == '':
|
if status.decode().find('unchanged') != -1 or status == '':
|
||||||
return False
|
return False
|
||||||
if status.decode().find('error') != -1:
|
if status.decode().find('error') != -1:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
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
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ class Photo(Media):
|
||||||
extensions = ('arw', 'cr2', 'dng', 'gif', 'heic', 'jpeg', 'jpg', 'nef', 'png', 'rw2')
|
extensions = ('arw', 'cr2', 'dng', 'gif', 'heic', 'jpeg', 'jpg', 'nef', 'png', 'rw2')
|
||||||
|
|
||||||
def __init__(self, source=None):
|
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
|
# We only want to parse EXIF once so we store it here
|
||||||
self.exif = None
|
self.exif = None
|
||||||
|
|
|
@ -28,31 +28,9 @@ class Video(Media):
|
||||||
extensions = ('avi', 'm4v', 'mov', 'mp4', 'mpg', 'mpeg', '3gp', 'mts')
|
extensions = ('avi', 'm4v', 'mov', 'mp4', 'mpg', 'mpeg', '3gp', 'mts')
|
||||||
|
|
||||||
def __init__(self, source=None):
|
def __init__(self, source=None):
|
||||||
super(Video, self).__init__(source)
|
super().__init__(source)
|
||||||
self.date_original = [
|
# self.set_gps_ref = False
|
||||||
'EXIF:DateTimeOriginal',
|
|
||||||
'H264:DateTimeOriginal',
|
|
||||||
'QuickTime:ContentCreateDate'
|
def is_valid(self):
|
||||||
]
|
return super().is_valid()
|
||||||
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
|
|
||||||
|
|
|
@ -9,8 +9,7 @@ from elodie import log
|
||||||
from elodie.compatability import _decode
|
from elodie.compatability import _decode
|
||||||
from elodie.filesystem import FileSystem
|
from elodie.filesystem import FileSystem
|
||||||
from elodie.localstorage import Db
|
from elodie.localstorage import Db
|
||||||
from elodie.media.base import Base, get_all_subclasses
|
from elodie.media.media import Media, get_all_subclasses
|
||||||
from elodie.media.media import Media
|
|
||||||
from elodie.media.audio import Audio
|
from elodie.media.audio import Audio
|
||||||
from elodie.media.photo import Photo
|
from elodie.media.photo import Photo
|
||||||
from elodie.media.video import Video
|
from elodie.media.video import Video
|
||||||
|
|
|
@ -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']
|
|
|
@ -8,7 +8,6 @@ import tempfile
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
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__)))))
|
sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))
|
||||||
|
|
||||||
import helper
|
import helper
|
||||||
|
@ -100,16 +99,16 @@ def test_set_date_original():
|
||||||
origin = '%s/audio.m4a' % folder
|
origin = '%s/audio.m4a' % folder
|
||||||
shutil.copyfile(helper.get_file('audio.m4a'), origin)
|
shutil.copyfile(helper.get_file('audio.m4a'), origin)
|
||||||
|
|
||||||
media = Media(origin)
|
audio = Audio(origin)
|
||||||
date = datetime(2013, 9, 30, 7, 6, 5)
|
date = datetime(2013, 9, 30, 7, 6, 5)
|
||||||
status = media.set_date_original(date)
|
status = audio.set_date_original(date)
|
||||||
|
|
||||||
assert status == True, status
|
assert status == True, status
|
||||||
|
|
||||||
audio_new = Audio(origin)
|
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)
|
shutil.rmtree(folder)
|
||||||
|
|
||||||
|
@ -134,7 +133,7 @@ def test_set_location():
|
||||||
assert status == True, status
|
assert status == True, status
|
||||||
|
|
||||||
audio_new = Audio(origin)
|
audio_new = Audio(origin)
|
||||||
metadata = audio_new.get_metadata()
|
metadata = audio_new.get_metadata(update_cache=True)
|
||||||
|
|
||||||
shutil.rmtree(folder)
|
shutil.rmtree(folder)
|
||||||
|
|
||||||
|
@ -160,7 +159,7 @@ def test_set_location_minus():
|
||||||
assert status == True, status
|
assert status == True, status
|
||||||
|
|
||||||
audio_new = Audio(origin)
|
audio_new = Audio(origin)
|
||||||
metadata = audio_new.get_metadata()
|
metadata = audio_new.get_metadata(update_cache=True)
|
||||||
|
|
||||||
shutil.rmtree(folder)
|
shutil.rmtree(folder)
|
||||||
|
|
||||||
|
@ -202,7 +201,7 @@ def test_set_title_non_ascii():
|
||||||
assert status == True, status
|
assert status == True, status
|
||||||
|
|
||||||
audio_new = Audio(origin)
|
audio_new = Audio(origin)
|
||||||
metadata = audio_new.get_metadata()
|
metadata = audio_new.get_metadata(update_cache=True)
|
||||||
|
|
||||||
shutil.rmtree(folder)
|
shutil.rmtree(folder)
|
||||||
|
|
|
@ -9,13 +9,13 @@ import shutil
|
||||||
import string
|
import string
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
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__)))))
|
sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))
|
||||||
|
|
||||||
import helper
|
import helper
|
||||||
|
from elodie.media.media import Media, get_all_subclasses
|
||||||
from elodie.media.audio import Audio
|
from elodie.media.audio import Audio
|
||||||
from elodie.media.media import Media
|
|
||||||
from elodie.media.photo import Photo
|
from elodie.media.photo import Photo
|
||||||
from elodie.media.video import Video
|
from elodie.media.video import Video
|
||||||
|
|
||||||
|
@ -24,6 +24,119 @@ os.environ['TZ'] = 'GMT'
|
||||||
setup_module = helper.setup_module
|
setup_module = helper.setup_module
|
||||||
teardown_module = helper.teardown_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():
|
def test_get_file_path():
|
||||||
media = Media(helper.get_file('plain.jpg'))
|
media = Media(helper.get_file('plain.jpg'))
|
||||||
path = media.get_file_path()
|
path = media.get_file_path()
|
||||||
|
@ -63,32 +176,6 @@ def test_get_class_by_file_invalid_type():
|
||||||
[Photo, Video, Audio])
|
[Photo, Video, Audio])
|
||||||
assert media is None
|
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():
|
def test_set_original_name_when_exists():
|
||||||
temporary_folder, folder = helper.create_working_folder()
|
temporary_folder, folder = helper.create_working_folder()
|
||||||
|
|
||||||
|
@ -98,7 +185,7 @@ def test_set_original_name_when_exists():
|
||||||
shutil.copyfile(file, origin)
|
shutil.copyfile(file, origin)
|
||||||
|
|
||||||
media = Media.get_class_by_file(origin, [Photo])
|
media = Media.get_class_by_file(origin, [Photo])
|
||||||
result = media.set_original_name()
|
result = media.set_original_name(origin)
|
||||||
|
|
||||||
assert result is None, result
|
assert result is None, result
|
||||||
|
|
||||||
|
@ -112,8 +199,8 @@ def test_set_original_name_when_does_not_exist():
|
||||||
|
|
||||||
media = Media.get_class_by_file(origin, [Photo])
|
media = Media.get_class_by_file(origin, [Photo])
|
||||||
metadata_before = media.get_metadata()
|
metadata_before = media.get_metadata()
|
||||||
result = media.set_original_name()
|
result = media.set_original_name(origin)
|
||||||
metadata_after = media.get_metadata()
|
metadata_after = media.get_metadata(update_cache=True)
|
||||||
|
|
||||||
assert metadata_before['original_name'] is None, metadata_before
|
assert metadata_before['original_name'] is None, metadata_before
|
||||||
assert metadata_after['original_name'] == 'plain.jpg', metadata_after
|
assert metadata_after['original_name'] == 'plain.jpg', metadata_after
|
||||||
|
@ -132,7 +219,7 @@ def test_set_original_name_with_arg():
|
||||||
media = Media.get_class_by_file(origin, [Photo])
|
media = Media.get_class_by_file(origin, [Photo])
|
||||||
metadata_before = media.get_metadata()
|
metadata_before = media.get_metadata()
|
||||||
result = media.set_original_name(new_name)
|
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_before['original_name'] is None, metadata_before
|
||||||
assert metadata_after['original_name'] == new_name, metadata_after
|
assert metadata_after['original_name'] == new_name, metadata_after
|
||||||
|
@ -154,10 +241,10 @@ def test_set_original_name():
|
||||||
|
|
||||||
shutil.copyfile(file_path, origin)
|
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()
|
metadata = media.get_metadata()
|
||||||
media.set_original_name()
|
media.set_original_name(origin)
|
||||||
metadata_updated = media.get_metadata()
|
metadata_updated = media.get_metadata(update_cache=True)
|
||||||
|
|
||||||
shutil.rmtree(folder)
|
shutil.rmtree(folder)
|
||||||
|
|
|
@ -10,7 +10,6 @@ import time
|
||||||
|
|
||||||
from nose.plugins.skip import SkipTest
|
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__)))))
|
sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))
|
||||||
|
|
||||||
import helper
|
import helper
|
||||||
|
@ -122,8 +121,8 @@ def test_get_coordinates_with_null_coordinate():
|
||||||
assert longitude is None, longitude
|
assert longitude is None, longitude
|
||||||
|
|
||||||
def test_get_date_original():
|
def test_get_date_original():
|
||||||
media = Media(helper.get_file('plain.jpg'))
|
photo = Photo(helper.get_file('plain.jpg'))
|
||||||
date_original = media.get_date_attribute(['EXIF:DateTimeOriginal'])
|
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 == (2015, 12, 5, 0, 59, 26, 5, 339, 0), date_original
|
||||||
assert date_original == datetime(2015, 12, 5, 0, 59, 26), 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']
|
assert metadata['album'] is None, metadata['album']
|
||||||
|
|
||||||
status = photo.set_album('Test Album')
|
status = photo.set_album('Test Album', origin)
|
||||||
|
|
||||||
assert status == True, status
|
assert status == True, status
|
||||||
|
|
||||||
photo_new = Photo(origin)
|
photo_new = Photo(origin)
|
||||||
metadata_new = photo_new.get_metadata()
|
metadata_new = photo_new.get_metadata(update_cache=True)
|
||||||
|
|
||||||
shutil.rmtree(folder)
|
shutil.rmtree(folder)
|
||||||
|
|
||||||
|
@ -193,14 +192,14 @@ def test_set_date_original_with_missing_datetimeoriginal():
|
||||||
origin = '%s/photo.jpg' % folder
|
origin = '%s/photo.jpg' % folder
|
||||||
shutil.copyfile(helper.get_file('no-exif.jpg'), origin)
|
shutil.copyfile(helper.get_file('no-exif.jpg'), origin)
|
||||||
|
|
||||||
media = Media(origin)
|
photo = Photo(origin)
|
||||||
time = datetime(2013, 9, 30, 7, 6, 5)
|
time = datetime(2013, 9, 30, 7, 6, 5)
|
||||||
status = media.set_date_original(time)
|
status = photo.set_date_original(time)
|
||||||
|
|
||||||
assert status == True, status
|
assert status == True, status
|
||||||
|
|
||||||
photo_new = Photo(origin)
|
photo_new = Photo(origin)
|
||||||
metadata = photo_new.get_metadata()
|
metadata = photo_new.get_metadata(update_cache=True)
|
||||||
|
|
||||||
date_original = metadata['date_original']
|
date_original = metadata['date_original']
|
||||||
|
|
||||||
|
@ -216,13 +215,13 @@ def test_set_date_original():
|
||||||
origin = '%s/photo.jpg' % folder
|
origin = '%s/photo.jpg' % folder
|
||||||
shutil.copyfile(helper.get_file('plain.jpg'), origin)
|
shutil.copyfile(helper.get_file('plain.jpg'), origin)
|
||||||
|
|
||||||
media = Media(origin)
|
photo = Photo(origin)
|
||||||
status = media.set_date_original(datetime(2013, 9, 30, 7, 6, 5))
|
status = photo.set_date_original(datetime(2013, 9, 30, 7, 6, 5))
|
||||||
|
|
||||||
assert status == True, status
|
assert status == True, status
|
||||||
|
|
||||||
media_new = Media(origin)
|
photo_new = Photo(origin)
|
||||||
metadata = media_new.get_metadata()
|
metadata = photo_new.get_metadata(update_cache=True)
|
||||||
|
|
||||||
date_original = metadata['date_original']
|
date_original = metadata['date_original']
|
||||||
|
|
||||||
|
@ -250,7 +249,7 @@ def test_set_location():
|
||||||
assert status == True, status
|
assert status == True, status
|
||||||
|
|
||||||
photo_new = Photo(origin)
|
photo_new = Photo(origin)
|
||||||
metadata = photo_new.get_metadata()
|
metadata = photo_new.get_metadata(update_cache=True)
|
||||||
|
|
||||||
shutil.rmtree(folder)
|
shutil.rmtree(folder)
|
||||||
|
|
||||||
|
@ -276,7 +275,7 @@ def test_set_location_minus():
|
||||||
assert status == True, status
|
assert status == True, status
|
||||||
|
|
||||||
photo_new = Photo(origin)
|
photo_new = Photo(origin)
|
||||||
metadata = photo_new.get_metadata()
|
metadata = photo_new.get_metadata(update_cache=True)
|
||||||
|
|
||||||
shutil.rmtree(folder)
|
shutil.rmtree(folder)
|
||||||
|
|
||||||
|
@ -297,7 +296,7 @@ def test_set_title():
|
||||||
assert status == True, status
|
assert status == True, status
|
||||||
|
|
||||||
photo_new = Photo(origin)
|
photo_new = Photo(origin)
|
||||||
metadata = photo_new.get_metadata()
|
metadata = photo_new.get_metadata(update_cache=True)
|
||||||
|
|
||||||
shutil.rmtree(folder)
|
shutil.rmtree(folder)
|
||||||
|
|
||||||
|
@ -318,7 +317,7 @@ def test_set_title_non_ascii():
|
||||||
assert status == True, status
|
assert status == True, status
|
||||||
|
|
||||||
photo_new = Photo(origin)
|
photo_new = Photo(origin)
|
||||||
metadata = photo_new.get_metadata()
|
metadata = photo_new.get_metadata(update_cache=True)
|
||||||
|
|
||||||
shutil.rmtree(folder)
|
shutil.rmtree(folder)
|
||||||
|
|
||||||
|
@ -395,7 +394,7 @@ def _test_photo_type_set(type, date):
|
||||||
assert status == True, status
|
assert status == True, status
|
||||||
|
|
||||||
photo_new = Photo(origin)
|
photo_new = Photo(origin)
|
||||||
metadata = photo_new.get_metadata()
|
metadata = photo_new.get_metadata(update_cache=True)
|
||||||
|
|
||||||
shutil.rmtree(folder)
|
shutil.rmtree(folder)
|
||||||
|
|
|
@ -9,7 +9,6 @@ import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from dateutil.parser import parse
|
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__)))))
|
sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))
|
||||||
|
|
||||||
import helper
|
import helper
|
||||||
|
@ -102,7 +101,7 @@ def test_set_album():
|
||||||
|
|
||||||
assert metadata['album'] is None, metadata['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
|
assert status == True, status
|
||||||
|
|
Loading…
Reference in New Issue