Refactoring media class (3)

This commit is contained in:
Cédric Leporcq 2021-11-11 16:24:37 +01:00
parent 8fd65fda34
commit 26845cf56b
4 changed files with 95 additions and 99 deletions

View File

@ -777,10 +777,10 @@ class Collection(SortMedias):
def init(self, loc): def init(self, loc):
"""Init collection db""" """Init collection db"""
for file_path in self.get_collection_files(): for file_path in self.get_collection_files():
media = self.medias.get_media(file_path, self.root, loc) metadata = self.medias.get_metadata(file_path, self.root, loc)
media.metadata['file_path'] = os.path.relpath(file_path, self.root) metadata['file_path'] = os.path.relpath(file_path, self.root)
self.db.add_file_data(media.metadata) self.db.add_file_data(metadata)
self.summary.append('update', file_path) self.summary.append('update', file_path)
return self.summary return self.summary
@ -832,22 +832,22 @@ class Collection(SortMedias):
relpath = os.path.relpath(file_path, self.root) relpath = os.path.relpath(file_path, self.root)
# If file not in database # If file not in database
if relpath not in db_paths: if relpath not in db_paths:
media = self.medias.get_media(file_path, self.root, loc) metadata = self.medias.get_metadata(file_path, self.root, loc)
media.metadata['file_path'] = relpath metadata['file_path'] = relpath
# Check if file checksum is in invalid rows # Check if file checksum is in invalid rows
row = [] row = []
for row in invalid_db_rows: for row in invalid_db_rows:
if row['Checksum'] == media.metadata['checksum']: if row['Checksum'] == metadata['checksum']:
# file have been moved without registering to db # file have been moved without registering to db
media.metadata['src_path'] = row['SrcPath'] metadata['src_path'] = row['SrcPath']
# Check if row FilePath is a subpath of relpath # Check if row FilePath is a subpath of relpath
if relpath.startswith(row['FilePath']): if relpath.startswith(row['FilePath']):
path = os.path.relpath(relpath, row['FilePath']) path = os.path.relpath(relpath, row['FilePath'])
media.metadata['subdirs'] = row['Subdirs'] + path metadata['subdirs'] = row['Subdirs'] + path
media.metadata['Filename'] = row['Filename'] metadata['Filename'] = row['Filename']
break break
# set row attribute to the file # set row attribute to the file
self.db.add_file_data(media.metadata) self.db.add_file_data(metadata)
self.summary.append('update', file_path) self.summary.append('update', file_path)
# Finally delete invalid rows # Finally delete invalid rows
@ -947,14 +947,13 @@ class Collection(SortMedias):
# Get medias data # Get medias data
subdirs = set() subdirs = set()
for media in self.medias.get_medias(src_dirs, imp=imp, loc=loc): for src_path, metadata in self.medias.get_metadatas(src_dirs, imp=imp, loc=loc):
# Get the destination path according to metadata # Get the destination path according to metadata
fpath = FPath(path_format, self.day_begins, self.logger) fpath = FPath(path_format, self.day_begins, self.logger)
media.metadata['file_path'] = fpath.get_path(media.metadata) metadata['file_path'] = fpath.get_path(metadata)
subdirs.add(media.file_path.parent) subdirs.add(src_path.parent)
src_path = media.file_path self.medias.datas[src_path] = copy(metadata)
self.medias.datas[src_path] = copy(media.metadata)
# Sort files and solve conflicts # Sort files and solve conflicts
self.summary = self.sort_medias(imp, remove_duplicates) self.summary = self.sort_medias(imp, remove_duplicates)
@ -990,9 +989,8 @@ class Collection(SortMedias):
dedup_regex = [date_num3, date_num2, default] dedup_regex = [date_num3, date_num2, default]
# Get medias data # Get medias data
for media in self.medias.get_medias(paths): for src_path, metadata in self.medias.get_metadatas(paths):
# Deduplicate the path # Deduplicate the path
src_path = media.file_path
path_parts = src_path.relative_to(self.root).parts path_parts = src_path.relative_to(self.root).parts
dedup_path = [] dedup_path = []
for path_part in path_parts: for path_part in path_parts:
@ -1005,9 +1003,8 @@ class Collection(SortMedias):
dedup_path.append(''.join(filtered_items)) dedup_path.append(''.join(filtered_items))
media.metadata['file_path'] = os.path.join(*dedup_path) metadata['file_path'] = os.path.join(*dedup_path)
src_path = media.file_path self.medias.datas[src_path] = copy(metadata)
self.medias.datas[src_path] = copy(media.metadata)
# Sort files and solve conflicts # Sort files and solve conflicts
self.sort_medias(remove_duplicates=remove_duplicates) self.sort_medias(remove_duplicates=remove_duplicates)
@ -1027,20 +1024,18 @@ class Collection(SortMedias):
for img_path in images.find_similar(image, similarity): for img_path in images.find_similar(image, similarity):
self.paths.paths_list.append(img_path) self.paths.paths_list.append(img_path)
media = self.medias.get_media(img_path, path) metadata = self.medias.get_metadata(img_path, path)
relpath = os.path.join(directory_name, img_path.name) relpath = os.path.join(directory_name, img_path.name)
media.metadata['file_path'] = relpath metadata['file_path'] = relpath
file_path = media.file_path self.medias.datas[img_path] = copy(metadata)
self.medias.datas[file_path] = copy(media.metadata)
if self.medias.datas: if self.medias.datas:
# Found similar images to image # Found similar images to image
self.paths.paths_list.append(image.img_path) self.paths.paths_list.append(image.img_path)
media = self.medias.get_media(image.img_path, path) metadata = self.medias.get_metadata(image.img_path, path)
relpath = os.path.join(directory_name, image.img_path.name) relpath = os.path.join(directory_name, image.img_path.name)
media.metadata['file_path'] = relpath metadata['file_path'] = relpath
file_path = media.file_path self.medias.datas[image.img_path] = copy(metadata)
self.medias.datas[file_path] = copy(media.metadata)
return True return True

View File

@ -69,6 +69,7 @@ class ExifMetadata:
class ReadExif(ExifMetadata): class ReadExif(ExifMetadata):
"""Read exif metadata to file"""
def __init__( def __init__(
self, self,
@ -94,7 +95,7 @@ class ReadExif(ExifMetadata):
return ExifToolCaching(self.file_path, logger=self.logger).asdict() return ExifToolCaching(self.file_path, logger=self.logger).asdict()
def _get_key_values(self, key): def get_key_values(self, key):
""" """
Get the first value of a tag set Get the first value of a tag set
:returns: str or None if no exif tag :returns: str or None if no exif tag
@ -106,20 +107,43 @@ class ReadExif(ExifMetadata):
if tag in self.exif_metadata: if tag in self.exif_metadata:
yield self.exif_metadata[tag] yield self.exif_metadata[tag]
def get_value(self, tag): def get_coordinates(self, key, value):
"""Get latitude or longitude value
:param str key: Type of coordinate to get. Either "latitude" or
"longitude".
:returns: float or None
""" """
Get given value from EXIF. if value is None:
:returns: str or None
"""
if self.exif_metadata is None:
return None
if tag not in self.exif_metadata:
return None return None
return self.exif_metadata[tag] if isinstance(value, str) and len(value) == 0:
# If exiftool GPS output is empty, the data returned will be a str
# with 0 length.
# https://github.com/jmathai/elodie/issues/354
return None
# Cast coordinate to a float due to a bug in exiftool's
# -json output format.
# https://github.com/jmathai/elodie/issues/171
# http://u88.n24.queensu.ca/exiftool/forum/index.php/topic,7952.0.html # noqa
this_coordinate = float(value)
direction_multiplier = 1.0
# when self.set_gps_ref != True
if key == 'latitude':
if 'EXIF:GPSLatitudeRef' in self.exif_metadata:
if self.exif_metadata['EXIF:GPSLatitudeRef'] == 'S':
direction_multiplier = -1.0
elif key == 'longitude':
if 'EXIF:GPSLongitudeRef' in self.exif_metadata:
if self.exif_metadata['EXIF:GPSLongitudeRef'] == 'W':
direction_multiplier = -1.0
return this_coordinate * direction_multiplier
class WriteExif(ExifMetadata): class WriteExif(ExifMetadata):
"""Write exif metadata to file"""
def __init__( def __init__(
self, self,
@ -140,6 +164,7 @@ class WriteExif(ExifMetadata):
:returns: value (str) :returns: value (str)
""" """
# TODO overwrite mode check if fail
return ExifTool(self.file_path, logger=self.logger).setvalue(tag, value) return ExifTool(self.file_path, logger=self.logger).setvalue(tag, value)
def set_key_values(self, key, value): def set_key_values(self, key, value):
@ -190,7 +215,7 @@ class WriteExif(ExifMetadata):
if all(status): if all(status):
return True return True
else:
return False return False
def set_album_from_folder(self): def set_album_from_folder(self):
@ -198,9 +223,9 @@ class WriteExif(ExifMetadata):
:returns: bool :returns: bool
""" """
# TODO use tag key
return self.set_value('Album', self.file_path.parent.name) return self.set_value('Album', self.file_path.parent.name)
class Media(ReadExif): class Media(ReadExif):
""" """
Extract matadatas from exiftool and sort them to dict structure Extract matadatas from exiftool and sort them to dict structure
@ -230,61 +255,25 @@ class Media(ReadExif):
self.album_from_folder = album_from_folder self.album_from_folder = album_from_folder
self.interactive = interactive self.interactive = interactive
self.logger = logger.getChild(self.__class__.__name__) self.logger = logger.getChild(self.__class__.__name__)
self.metadata = None
self.use_date_filename = use_date_filename self.use_date_filename = use_date_filename
self.use_file_dates = use_file_dates self.use_file_dates = use_file_dates
self.theme = request.load_theme() self.theme = request.load_theme()
# get self.metadata
self.get_metadata(self.file_path)
def get_mimetype(self): def get_mimetype(self):
""" """
Get the mimetype of the file. Get the mimetype of the file.
:returns: str or None :returns: str or None
""" """
# TODO add to metadata
mimetype = mimetypes.guess_type(self.file_path) mimetype = mimetypes.guess_type(self.file_path)
if mimetype is None: if mimetype is None:
return None return None
return mimetype[0] return mimetype[0]
def get_coordinates(self, key, value):
"""Get latitude or longitude value
:param str key: Type of coordinate to get. Either "latitude" or
"longitude".
:returns: float or None
"""
if value is None:
return None
if isinstance(value, str) and len(value) == 0:
# If exiftool GPS output is empty, the data returned will be a str
# with 0 length.
# https://github.com/jmathai/elodie/issues/354
return None
# Cast coordinate to a float due to a bug in exiftool's
# -json output format.
# https://github.com/jmathai/elodie/issues/171
# http://u88.n24.queensu.ca/exiftool/forum/index.php/topic,7952.0.html # noqa
this_coordinate = float(value)
direction_multiplier = 1.0
# when self.set_gps_ref != True
if key == 'latitude':
if 'EXIF:GPSLatitudeRef' in self.exif_metadata:
if self.exif_metadata['EXIF:GPSLatitudeRef'] == 'S':
direction_multiplier = -1.0
elif key == 'longitude':
if 'EXIF:GPSLongitudeRef' in self.exif_metadata:
if self.exif_metadata['EXIF:GPSLongitudeRef'] == 'W':
direction_multiplier = -1.0
return this_coordinate * direction_multiplier
return None
def get_date_format(self, value): def get_date_format(self, value):
""" """
Formatting date attribute. Formatting date attribute.
@ -311,11 +300,12 @@ class Media(ReadExif):
choices_list = [ choices_list = [
inquirer.List( inquirer.List(
'date_list', 'date_list',
message=f"Choice appropriate original date", message="Choice appropriate original date",
choices=choices, choices=choices,
default=default, default=default,
), ),
] ]
# import ipdb; ipdb.set_trace()
answers = inquirer.prompt(choices_list, theme=self.theme) answers = inquirer.prompt(choices_list, theme=self.theme)
if not answers['date_list']: if not answers['date_list']:
@ -324,7 +314,7 @@ class Media(ReadExif):
] ]
answers = inquirer.prompt(prompt, theme=self.theme) answers = inquirer.prompt(prompt, theme=self.theme)
return self.get_date_format(answers['date_custom']) return self.get_date_format(answers['date_custom'])
else:
return answers['date_list'] return answers['date_list']
def get_date_media(self): def get_date_media(self):
@ -384,17 +374,19 @@ class Media(ReadExif):
return date_filename return date_filename
elif self.use_file_dates: if self.use_file_dates:
if date_created: if date_created:
self.logger.warning( self.logger.warning(
f"use date created:{date_created} for {self.file_path}" f"use date created:{date_created} for {self.file_path}"
) )
return date_created return date_created
elif date_modified:
if date_modified:
self.logger.warning( self.logger.warning(
f"use date modified:{date_modified} for {self.file_path}" f"use date modified:{date_modified} for {self.file_path}"
) )
return date_modified return date_modified
elif self.interactive: elif self.interactive:
choices = [] choices = []
if date_filename: if date_filename:
@ -441,7 +433,7 @@ class Media(ReadExif):
for key in self.tags_keys: for key in self.tags_keys:
formated_data = None formated_data = None
for value in self._get_key_values(key): for value in self.get_key_values(key):
if 'date' in key: if 'date' in key:
formated_data = self.get_date_format(value) formated_data = self.get_date_format(value)
elif key in ('latitude', 'longitude'): elif key in ('latitude', 'longitude'):
@ -485,7 +477,8 @@ class Media(ReadExif):
return db.get_metadata_data(relpath, 'LocationId') return db.get_metadata_data(relpath, 'LocationId')
def _check_file(self, db, root): def _check_file(self, db, root):
# Check if file_path is a subpath of root """Check if file_path is a subpath of root"""
if str(self.file_path).startswith(str(root)): if str(self.file_path).startswith(str(root)):
relpath = os.path.relpath(self.file_path, root) relpath = os.path.relpath(self.file_path, root)
db_checksum = db.get_checksum(relpath) db_checksum = db.get_checksum(relpath)
@ -506,7 +499,7 @@ class Media(ReadExif):
return relpath, db_checksum return relpath, db_checksum
return return None, None
def _set_location_metadata(self, location_id, db, loc=None): def _set_location_metadata(self, location_id, db, loc=None):
@ -653,8 +646,8 @@ class Medias:
self.datas = {} self.datas = {}
self.theme = request.load_theme() self.theme = request.load_theme()
def get_media(self, file_path, src_dir, loc=None): def get_media(self, file_path, src_dir):
return Media( media = Media(
file_path, file_path,
src_dir, src_dir,
self.album_from_folder, self.album_from_folder,
@ -665,7 +658,16 @@ class Medias:
self.use_file_dates, self.use_file_dates,
) )
def get_medias(self, src_dirs, imp=False, loc=None): return media
def get_metadata(self, file_path, src_dir, loc=None):
media = self.get_media(file_path, src_dir)
media.get_metadata(self.root, loc, self.db.sqlite,
self.cache)
return media.metadata
def get_metadatas(self, src_dirs, imp=False, loc=None):
"""Get medias data""" """Get medias data"""
for src_dir in src_dirs: for src_dir in src_dirs:
src_dir = self.paths.check(src_dir) src_dir = self.paths.check(src_dir)
@ -680,9 +682,9 @@ class Medias:
sys.exit(1) sys.exit(1)
# Get file metadata # Get file metadata
media = self.get_media(src_path, src_dir, loc) metadata = self.get_metadata(src_path, src_dir, loc)
yield media yield src_path, metadata
def update_exif_data(self, metadata): def update_exif_data(self, metadata):
@ -711,5 +713,3 @@ class Medias:
return True return True
return False return False

View File

@ -1,4 +1,3 @@
# TODO to be removed later
from datetime import datetime from datetime import datetime
import shutil import shutil
import sqlite3 import sqlite3
@ -12,7 +11,7 @@ from ordigi.collection import Collection, FPath, Paths
from ordigi.exiftool import ExifToolCaching, exiftool_is_running, terminate_exiftool from ordigi.exiftool import ExifToolCaching, exiftool_is_running, terminate_exiftool
from ordigi.geolocation import GeoLocation from ordigi.geolocation import GeoLocation
from ordigi import log from ordigi import log
from ordigi.media import Media from ordigi.media import Media, ReadExif
from ordigi import utils from ordigi import utils
from .conftest import randomize_files, randomize_db from .conftest import randomize_files, randomize_db
from ordigi.summary import Summary from ordigi.summary import Summary
@ -65,12 +64,12 @@ class TestFPath:
exif_data = ExifToolCaching(str(file_path)).asdict() exif_data = ExifToolCaching(str(file_path)).asdict()
loc = GeoLocation() loc = GeoLocation()
metadata = media.metadata media.get_metadata(self.src_path, loc)
for item, regex in items.items(): for item, regex in items.items():
for mask in masks: for mask in masks:
matched = re.search(regex, mask) matched = re.search(regex, mask)
if matched: if matched:
part = fpath.get_part(item, mask[1:-1], metadata) part = fpath.get_part(item, mask[1:-1], media.metadata)
# check if part is correct # check if part is correct
assert isinstance(part, str), file_path assert isinstance(part, str), file_path
if item == 'basename': if item == 'basename':
@ -157,7 +156,7 @@ class TestCollection:
for file_path in paths: for file_path in paths:
if '.db' not in str(file_path): if '.db' not in str(file_path):
media = Media(file_path, tmp_path, album_from_folder=True) media = Media(file_path, tmp_path, album_from_folder=True)
for value in media._get_key_values('album'): for value in ReadExif(file_path).get_key_values('album'):
assert value != '' or None assert value != '' or None
collection = Collection(tmp_path, album_from_folder=True) collection = Collection(tmp_path, album_from_folder=True)
@ -206,6 +205,7 @@ class TestCollection:
# copy mode # copy mode
src_path = Path(self.src_path, 'test_exif', 'photo.png') src_path = Path(self.src_path, 'test_exif', 'photo.png')
media = Media(src_path, self.src_path) media = Media(src_path, self.src_path)
media.get_metadata(tmp_path)
name = 'photo_' + str(imp) + '.png' name = 'photo_' + str(imp) + '.png'
media.metadata['file_path'] = name media.metadata['file_path'] = name
dest_path = Path(tmp_path, name) dest_path = Path(tmp_path, name)

View File

@ -52,7 +52,7 @@ class TestMedia:
assert value is None assert value is None
if key == 'album': if key == 'album':
for album in media._get_key_values('album'): for album in media.get_key_values('album'):
if album is not None and album != '': if album is not None and album != '':
assert value == album assert value == album
break break
@ -76,6 +76,7 @@ class TestMedia:
exif_data = ExifToolCaching(str(file_path)).asdict() exif_data = ExifToolCaching(str(file_path)).asdict()
media = Media(file_path, self.src_path, use_date_filename=True, media = Media(file_path, self.src_path, use_date_filename=True,
use_file_dates=True) use_file_dates=True)
media.get_metadata(self.src_path)
date_media = media.get_date_media() date_media = media.get_date_media()
date_filename = None date_filename = None