Drop textfile support

This commit is contained in:
Cédric Leporcq 2021-06-19 15:42:28 +02:00
parent da764e94e6
commit 8bc8b6c5c4
13 changed files with 3 additions and 590 deletions

View File

@ -24,7 +24,6 @@ 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.base import Base, get_all_subclasses
from elodie.media.media import Media from elodie.media.media import Media
from elodie.media.text import Text
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

View File

@ -3,8 +3,7 @@ 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 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 by all the media types, but isn't itself used to represent anything. Its
sub-classes (:class:`~elodie.media.audio.Audio`, sub-classes (:class:`~elodie.media.audio.Audio`,
:class:`~elodie.media.photo.Photo`, :class:`~elodie.media.video.Video`, and :class:`~elodie.media.photo.Photo`, :class:`~elodie.media.video.Video`)
:class:`~elodie.media.text.Text`)
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>

View File

@ -1,203 +0,0 @@
"""
The text module provides a base :class:`Text` class for text files that
are tracked by Elodie.
.. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com>
"""
from json import dumps, loads
import os
from shutil import copy2, copyfileobj
import time
# load modules
from elodie import log
from elodie.media.base import Base
class Text(Base):
"""The class for all text files.
:param str source: The fully qualified path to the text file.
"""
__name__ = 'Text'
#: Valid extensions for text files.
extensions = ('txt',)
def __init__(self, source=None):
super(Text, self).__init__(source)
self.reset_cache()
def get_album(self):
self.parse_metadata_line()
if(not isinstance(self.metadata_line, dict) or
'album' not in self.metadata_line):
return None
return self.metadata_line['album']
def get_coordinate(self, type='latitude'):
self.parse_metadata_line()
if not self.metadata_line:
return None
elif type in self.metadata_line:
if type == 'latitude':
return self.metadata_line['latitude'] or None
elif type == 'longitude':
return self.metadata_line['longitude'] or None
return None
def get_date_taken(self):
source = self.source
self.parse_metadata_line()
# We return the value if found in metadata
if(isinstance(self.metadata_line, dict) and
'date_taken' in self.metadata_line):
return time.gmtime(self.metadata_line['date_taken'])
# If there's no date_taken in the metadata we return
# from the filesystem
seconds_since_epoch = min(
os.path.getmtime(source),
os.path.getctime(source)
)
return time.gmtime(seconds_since_epoch)
def get_metadata(self):
self.parse_metadata_line()
return super(Text, self).get_metadata()
def get_original_name(self):
self.parse_metadata_line()
# We return the value if found in metadata
if(isinstance(self.metadata_line, dict) and
'original_name' in self.metadata_line):
return self.metadata_line['original_name']
return super(Text, self).get_original_name()
def get_title(self):
self.parse_metadata_line()
if(not isinstance(self.metadata_line, dict) or
'title' not in self.metadata_line):
return None
return self.metadata_line['title']
def reset_cache(self):
"""Resets any internal cache
"""
self.metadata_line = None
super(Text, self).reset_cache()
def set_album(self, name):
status = self.write_metadata(album=name)
self.reset_cache()
return status
def set_date_taken(self, passed_in_time):
if(time is None):
return False
seconds_since_epoch = time.mktime(passed_in_time.timetuple())
status = self.write_metadata(date_taken=seconds_since_epoch)
self.reset_cache()
return status
def set_original_name(self, name=None):
"""Sets the original name if not already set.
:returns: True, False, None
"""
if(not self.is_valid()):
return None
# If EXIF original name tag is set then we return.
if self.get_original_name() is not None:
return None
source = self.source
if not name:
name = os.path.basename(source)
status = self.write_metadata(original_name=name)
self.reset_cache()
return status
def set_location(self, latitude, longitude):
status = self.write_metadata(latitude=latitude, longitude=longitude)
self.reset_cache()
return status
def parse_metadata_line(self):
if isinstance(self.metadata_line, dict):
return self.metadata_line
source = self.source
if source is None:
return None
with open(source, 'r') as f:
first_line = f.readline().strip()
try:
parsed_json = loads(first_line)
if isinstance(parsed_json, dict):
self.metadata_line = parsed_json
except ValueError:
log.error('Could not parse JSON from first line: %s' % first_line)
pass
def write_metadata(self, **kwargs):
if len(kwargs) == 0:
return False
source = self.source
self.parse_metadata_line()
# Set defaults for a file without metadata
# Check if self.metadata_line is set and use that instead
metadata_line = {}
has_metadata = False
if isinstance(self.metadata_line, dict):
metadata_line = self.metadata_line
has_metadata = True
for name in kwargs:
metadata_line[name] = kwargs[name]
metadata_as_json = dumps(metadata_line)
# Create an _original copy just as we do with exiftool
# This is to keep all file processing logic in line with exiftool
copy2(source, source + '_original')
if has_metadata:
# Update the first line of this file in place
# http://stackoverflow.com/a/14947384
with open(source, 'r') as f_read:
f_read.readline()
with open(source, 'w') as f_write:
f_write.write("{}\n".format(metadata_as_json))
copyfileobj(f_read, f_write)
else:
# Prepend the metadata to the file
with open(source, 'r') as f_read:
original_contents = f_read.read()
with open(source, 'w') as f_write:
f_write.write("{}\n{}".format(
metadata_as_json,
original_contents)
)
self.reset_cache()
return True

View File

@ -21,7 +21,6 @@ from elodie.config import load_config
from elodie.localstorage import Db from elodie.localstorage import Db
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.text import Text
from elodie.media.video import Video from elodie.media.video import Video
from elodie.plugins.plugins import Plugins from elodie.plugins.plugins import Plugins
from elodie.plugins.googlephotos.googlephotos import GooglePhotos from elodie.plugins.googlephotos.googlephotos import GooglePhotos
@ -460,31 +459,6 @@ def test_update_location_on_photo():
assert helper.isclose(metadata_processed['latitude'], 37.36883), metadata_processed['latitude'] assert helper.isclose(metadata_processed['latitude'], 37.36883), metadata_processed['latitude']
assert helper.isclose(metadata_processed['longitude'], -122.03635), metadata_processed['longitude'] assert helper.isclose(metadata_processed['longitude'], -122.03635), metadata_processed['longitude']
def test_update_location_on_text():
temporary_folder, folder = helper.create_working_folder()
temporary_folder_destination, folder_destination = helper.create_working_folder()
origin = '%s/text.txt' % folder
shutil.copyfile(helper.get_file('text.txt'), origin)
text = Text(origin)
metadata = text.get_metadata()
helper.reset_dbs()
status = elodie.update_location(text, origin, 'Sunnyvale, CA')
helper.restore_dbs()
text_processed = Text(origin)
metadata_processed = text_processed.get_metadata()
shutil.rmtree(folder)
shutil.rmtree(folder_destination)
assert status == True, status
assert metadata['latitude'] != metadata_processed['latitude']
assert helper.isclose(metadata_processed['latitude'], 37.36883), metadata_processed['latitude']
assert helper.isclose(metadata_processed['longitude'], -122.03635), metadata_processed['longitude']
def test_update_location_on_video(): def test_update_location_on_video():
temporary_folder, folder = helper.create_working_folder() temporary_folder, folder = helper.create_working_folder()
temporary_folder_destination, folder_destination = helper.create_working_folder() temporary_folder_destination, folder_destination = helper.create_working_folder()
@ -558,30 +532,6 @@ def test_update_time_on_photo():
assert metadata['date_taken'] != metadata_processed['date_taken'] assert metadata['date_taken'] != metadata_processed['date_taken']
assert metadata_processed['date_taken'] == helper.time_convert((2000, 1, 1, 12, 0, 0, 5, 1, 0)), metadata_processed['date_taken'] assert metadata_processed['date_taken'] == helper.time_convert((2000, 1, 1, 12, 0, 0, 5, 1, 0)), metadata_processed['date_taken']
def test_update_time_on_text():
temporary_folder, folder = helper.create_working_folder()
temporary_folder_destination, folder_destination = helper.create_working_folder()
origin = '%s/text.txt' % folder
shutil.copyfile(helper.get_file('text.txt'), origin)
text = Text(origin)
metadata = text.get_metadata()
helper.reset_dbs()
status = elodie.update_time(text, origin, '2000-01-01 12:00:00')
helper.restore_dbs()
text_processed = Text(origin)
metadata_processed = text_processed.get_metadata()
shutil.rmtree(folder)
shutil.rmtree(folder_destination)
assert status == True, status
assert metadata['date_taken'] != metadata_processed['date_taken']
assert metadata_processed['date_taken'] == helper.time_convert((2000, 1, 1, 12, 0, 0, 5, 1, 0)), metadata_processed['date_taken']
def test_update_time_on_video(): def test_update_time_on_video():
temporary_folder, folder = helper.create_working_folder() temporary_folder, folder = helper.create_working_folder()
temporary_folder_destination, folder_destination = helper.create_working_folder() temporary_folder_destination, folder_destination = helper.create_working_folder()

View File

@ -1,4 +0,0 @@
1234567890
See gh-98

View File

@ -1 +0,0 @@
This file has no header.

View File

@ -1,3 +0,0 @@
{"date_taken":1460027726.0,"latitude":"51.521435","longitude":"0.162714","title":"sample title"}
This file has a valid header.

View File

@ -1,3 +0,0 @@
{"date_taken":1460027726.0,"latitude":"51.521435","longitude":"0.162714","title":"sample title","original_name":"originalname.txt"}
This file has a valid header.

View File

@ -15,7 +15,6 @@ sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirna
from . import helper from . import helper
from elodie.config import load_config from elodie.config import load_config
from elodie.filesystem import FileSystem from elodie.filesystem import FileSystem
from elodie.media.text import Text
from elodie.media.media import Media 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

View File

@ -17,7 +17,6 @@ import helper
from elodie.media.base import Base, get_all_subclasses from elodie.media.base import Base, get_all_subclasses
from elodie.media.media import Media from elodie.media.media import Media
from elodie.media.audio import Audio from elodie.media.audio import Audio
from elodie.media.text import Text
from elodie.media.photo import Photo from elodie.media.photo import Photo
from elodie.media.video import Video from elodie.media.video import Video
@ -28,13 +27,13 @@ teardown_module = helper.teardown_module
def test_get_all_subclasses(): def test_get_all_subclasses():
subclasses = get_all_subclasses(Base) subclasses = get_all_subclasses(Base)
expected = {Media, Base, Text, Photo, Video, Audio} expected = {Media, Base, Photo, Video, Audio}
assert subclasses == expected, subclasses assert subclasses == expected, subclasses
def test_get_class_by_file_without_extension(): def test_get_class_by_file_without_extension():
base_file = helper.get_file('withoutextension') base_file = helper.get_file('withoutextension')
cls = Base.get_class_by_file(base_file, [Audio, Text, Photo, Video]) cls = Base.get_class_by_file(base_file, [Audio, Photo, Video])
assert cls is None, cls assert cls is None, cls

View File

@ -1,318 +0,0 @@
# -*- coding: utf-8
# Project imports
import os
import sys
from datetime import datetime
import shutil
import tempfile
import time
from nose.plugins.skip import SkipTest
sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))))
sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))
import helper
from elodie.media.base import Base
from elodie.media.text import Text
os.environ['TZ'] = 'GMT'
def test_text_extensions():
text = Text()
extensions = text.extensions
assert 'txt' in extensions
valid_extensions = Text.get_valid_extensions()
assert extensions == valid_extensions, valid_extensions
def test_get_original_name():
media = Text(helper.get_file('with-original-name.txt'))
original_name = media.get_original_name()
assert original_name == 'originalname.txt', original_name
def test_get_original_name_when_does_not_exist():
media = Text(helper.get_file('valid.txt'))
original_name = media.get_original_name()
assert original_name is None, original_name
def test_get_title():
text = Text(helper.get_file('valid.txt'))
text.get_metadata()
assert text.get_title() == 'sample title', text.get_title()
def test_get_default_coordinate():
text = Text(helper.get_file('valid.txt'))
text.get_metadata()
assert text.get_coordinate() == '51.521435', text.get_coordinate()
def test_get_coordinate_latitude():
text = Text(helper.get_file('valid.txt'))
text.get_metadata()
assert text.get_coordinate('latitude') == '51.521435', text.get_coordinate('latitude')
def test_get_coordinate_longitude():
text = Text(helper.get_file('valid.txt'))
text.get_metadata()
assert text.get_coordinate('longitude') == '0.162714', text.get_coordinate('longitude')
def test_get_date_taken():
text = Text(helper.get_file('valid.txt'))
text.get_metadata()
date_taken = text.get_date_taken()
assert date_taken == helper.time_convert((2016, 4, 7, 11, 15, 26, 3, 98, 0)), date_taken
def test_get_date_taken_from_invalid():
origin = helper.get_file('valid-without-header.txt')
text = Text(origin)
text.get_metadata()
date_taken = text.get_date_taken()
seconds_since_epoch = min(
os.path.getmtime(origin),
os.path.getctime(origin)
)
expected_date_taken = time.gmtime(seconds_since_epoch)
assert date_taken == expected_date_taken, date_taken
def test_get_metadata_with_numeric_header():
# See gh-98 for details
text = Text(helper.get_file('valid-with-numeric-header.txt'))
# Should not throw error
# TypeError: argument of type 'int' is not iterable
metadata = text.get_metadata()
assert metadata['mime_type'] == 'text/plain'
def test_set_album():
temporary_folder, folder = helper.create_working_folder()
origin = '%s/text.txt' % folder
shutil.copyfile(helper.get_file('valid.txt'), origin)
text = Text(origin)
metadata = text.get_metadata()
with open(origin, 'r') as f:
f.readline()
contents = f.read()
album_name = 'Test Album'
assert album_name != metadata['album']
status = text.set_album(album_name)
assert status == True, status
text_new = Text(origin)
metadata_new = text_new.get_metadata()
with open(origin, 'r') as f:
f.readline()
contents_new = f.read()
assert contents == contents_new, contents_new
shutil.rmtree(folder)
assert album_name == metadata_new['album'], metadata_new
def test_set_date_taken():
temporary_folder, folder = helper.create_working_folder()
origin = '%s/text.txt' % folder
shutil.copyfile(helper.get_file('valid.txt'), origin)
text = Text(origin)
metadata = text.get_metadata()
with open(origin, 'r') as f:
f.readline()
contents = f.read()
assert helper.time_convert((2013, 9, 30, 7, 6, 5, 0, 273, 0)) != metadata['date_taken'], metadata['date_taken']
status = text.set_date_taken(datetime(2013, 9, 30, 7, 6, 5))
assert status == True, status
text_new = Text(origin)
metadata_new = text_new.get_metadata()
with open(origin, 'r') as f:
f.readline()
contents_new = f.read()
assert contents == contents_new, contents_new
shutil.rmtree(folder)
assert helper.time_convert((2013, 9, 30, 7, 6, 5, 0, 273, 0)) == metadata_new['date_taken'], metadata_new['date_taken']
def test_set_location():
temporary_folder, folder = helper.create_working_folder()
origin = '%s/text.txt' % folder
shutil.copyfile(helper.get_file('valid.txt'), origin)
text = Text(origin)
origin_metadata = text.get_metadata()
with open(origin, 'r') as f:
f.readline()
contents = f.read()
# Verify that original photo has different location info that what we
# will be setting and checking
assert not helper.isclose(origin_metadata['latitude'], 11.1111111111), origin_metadata['latitude']
assert not helper.isclose(origin_metadata['longitude'], 99.9999999999), origin_metadata['longitude']
status = text.set_location(11.1111111111, 99.9999999999)
assert status == True, status
text_new = Text(origin)
metadata = text_new.get_metadata()
with open(origin, 'r') as f:
f.readline()
contents_new = f.read()
assert contents == contents_new, contents_new
shutil.rmtree(folder)
assert helper.isclose(metadata['latitude'], 11.1111111111), metadata['latitude']
def test_set_album_without_header():
temporary_folder, folder = helper.create_working_folder()
origin = '%s/text.txt' % folder
shutil.copyfile(helper.get_file('valid-without-header.txt'), origin)
text = Text(origin)
metadata = text.get_metadata()
with open(origin, 'r') as f:
contents = f.read()
album_name = 'Test Album'
assert album_name != metadata['album']
status = text.set_album(album_name)
assert status == True, status
text_new = Text(origin)
metadata_new = text_new.get_metadata()
with open(origin, 'r') as f:
f.readline()
contents_new = f.read()
assert contents == contents_new, contents_new
shutil.rmtree(folder)
assert album_name == metadata_new['album'], metadata_new
def test_set_date_taken_without_header():
temporary_folder, folder = helper.create_working_folder()
origin = '%s/text.txt' % folder
shutil.copyfile(helper.get_file('valid-without-header.txt'), origin)
text = Text(origin)
metadata = text.get_metadata()
with open(origin, 'r') as f:
contents = f.read()
assert helper.time_convert((2013, 9, 30, 7, 6, 5, 0, 273, 0)) != metadata['date_taken'], metadata['date_taken']
status = text.set_date_taken(datetime(2013, 9, 30, 7, 6, 5))
assert status == True, status
text_new = Text(origin)
metadata_new = text_new.get_metadata()
with open(origin, 'r') as f:
f.readline()
contents_new = f.read()
assert contents == contents_new, contents_new
shutil.rmtree(folder)
assert helper.time_convert((2013, 9, 30, 7, 6, 5, 0, 273, 0)) == metadata_new['date_taken'], metadata_new['date_taken']
def test_set_location_without_header():
temporary_folder, folder = helper.create_working_folder()
origin = '%s/text.txt' % folder
shutil.copyfile(helper.get_file('valid-without-header.txt'), origin)
text = Text(origin)
origin_metadata = text.get_metadata()
with open(origin, 'r') as f:
contents = f.read()
# Verify that original photo has different location info that what we
# will be setting and checking
assert not helper.isclose(origin_metadata['latitude'], 11.1111111111), origin_metadata['latitude']
assert not helper.isclose(origin_metadata['longitude'], 99.9999999999), origin_metadata['longitude']
status = text.set_location(11.1111111111, 99.9999999999)
assert status == True, status
text_new = Text(origin)
metadata = text_new.get_metadata()
with open(origin, 'r') as f:
f.readline()
contents_new = f.read()
assert contents == contents_new, contents_new
shutil.rmtree(folder)
assert helper.isclose(metadata['latitude'], 11.1111111111), metadata['latitude']
def test_set_original_name():
temporary_folder, folder = helper.create_working_folder()
random_file_name = '%s.txt' % helper.random_string(10)
origin = '%s/%s' % (folder, random_file_name)
shutil.copyfile(helper.get_file('valid.txt'), origin)
text = Text(origin)
metadata = text.get_metadata()
text.set_original_name()
metadata_updated = text.get_metadata()
shutil.rmtree(folder)
assert metadata['original_name'] is None, metadata['original_name']
assert metadata_updated['original_name'] == random_file_name, metadata_updated['original_name']
def test_set_original_name_with_arg():
temporary_folder, folder = helper.create_working_folder()
random_file_name = '%s.txt' % helper.random_string(10)
origin = '%s/%s' % (folder, random_file_name)
shutil.copyfile(helper.get_file('valid.txt'), origin)
new_name = helper.random_string(15)
text = Text(origin)
metadata = text.get_metadata()
text.set_original_name(new_name)
metadata_updated = text.get_metadata()
shutil.rmtree(folder)
assert metadata['original_name'] is None, metadata['original_name']
assert metadata_updated['original_name'] == new_name, metadata_updated['original_name']

View File

@ -12,7 +12,6 @@ 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.base import Base, get_all_subclasses
from elodie.media.media import Media from elodie.media.media import Media
from elodie.media.text import Text
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