radiation-tagger/functions.py

176 lines
6.4 KiB
Python
Raw Normal View History

#!/usr/bin/env python
# -*- coding: utf-8 -*-
''' Classes used by main program. '''
from datetime import datetime
import os
import shutil
from fractions import Fraction
import pyexiv2
class Radiation:
'''
Reiceives Values vom CSV file and creates a list of the relevant data
Arguments:
timestamp: Date/time string from CSV as string
radiation: Radiation from CSV in CP/M as float
local_timezone: timezone for timezone-unware CSV / Photo, if GPX is timezone aware
si_factor: CP/M to (µS/h) conversion factor - specific to GMC-tube
Returns:
timestamp: timestamp of CSV value als datetime object
radiation: radiation in µS/h as str (for Exif comment, UTF-8)
'''
def __init__(self, timestamp, radiation, local_timezone, si_factor):
self.timestamp = self._time_conversion(timestamp, local_timezone)
self.radiation = self._radiation_conversion(radiation, si_factor)
def __repr__(self):
return '%s %f µS/h' % (str(self.timestamp), self.radiation)
def _time_conversion(self, timestamp, local_timezone):
csv_naive_time = datetime.fromisoformat(timestamp)
# Set timezone
csv_aware_time = csv_naive_time.astimezone(local_timezone)
return csv_aware_time
def _radiation_conversion(self, radiation, si_factor):
# Convert CP/M to µS/h using si_factor
radiation = round(float(radiation) * si_factor, 2)
return radiation
class Photo:
'''
Reads Exif metadata.
Arguments:
photo: source photo ()
local_timezone: timezone for timezone-unware CSV / Photo, if GPX is timezone aware
dest_dir: destination directory where the photo is going to be copied to.
dry_run: whether to acutally write (True / False)
Returns:
self.get_date: timestamp of photo als datetime object
self.get_target_photo: full path to photo file to work on
'''
def __init__(self, photo, local_timezone, dest_dir, dry_run):
self.get_date = self._get_creation_date(photo, local_timezone)
self.get_target_photo = self._copy_photo(photo, dest_dir, dry_run)
def __repr__(self):
return 'Photo: %s Creation Date: %s' % (str(self.get_target_photo), str(self.get_date))
def _copy_photo(self, photo, dest_dir, dry_run):
# Determine where to work on photo and copy it there if needed.
# Get image file name out of path
photo_basename = os.path.basename(photo)
# be os aware and use the correct directory delimiter for destfile
dest_photo = os.path.join(dest_dir, photo_basename)
# Copy photo to dest_dir and return its (new) filename
# if not in dry_run mode or if dest_dir is different from src_dir.
if dry_run is True:
return photo
if dest_dir != '.':
shutil.copy(photo, dest_photo)
return dest_photo
def _get_creation_date(self, photo, local_timezone):
# Load Exif data from photo
metadata = pyexiv2.ImageMetadata(photo)
metadata.read()
date = metadata['Exif.Photo.DateTimeOriginal']
# date.value creates datetime object in pic_naive_time
pic_naive_time = date.value
# Set timezone
pic_aware_time = pic_naive_time.astimezone(local_timezone)
return pic_aware_time
class Exif:
'''
Converts, compiles and writes Exif-Tags from given arguemnts.
Arguments:
photo: file name of photo to modify
radiation: radiation levels in µS/h
latitude: latitude as float
longitude: longitude as float
elevation: elevation as float
dry_run: whether to acutally write (True / False)
Returns:
Latitude / Longitude in degrees
Exif-Comment that has been written (incl. radiation)
'''
def __init__(self, photo, dry_run, radiation=False, latitude=False, longitude=False, elevation=False):
self.write_exif = self._write_exif(photo, radiation, latitude,
longitude, elevation, dry_run)
def __repr__(self):
return 'Position: %s, %s: %s ' % self.write_exif
def _to_degree(self, value, loc):
if value < 0:
loc_value = loc[0]
elif value > 0:
loc_value = loc[1]
else:
loc_value = ""
abs_value = abs(value)
deg = int(abs_value)
t1 = (abs_value - deg) * 60
minute = int(t1)
second = round((t1 - minute) * 60, 5)
return (deg, minute, second, loc_value)
def _write_exif(self, photo, dry_run, radiation, latitude, longitude, elevation):
metadata = pyexiv2.ImageMetadata(photo)
metadata.read()
if latitude or longitude:
latitude_degree = self._to_degree(latitude, ["S", "N"])
longitude_degree = self._to_degree(longitude, ["W", "E"])
# convert decimal coordinates into fractions required for pyexiv2
exiv2_latitude = (Fraction(latitude_degree[0] * 60 + latitude_degree[1], 60),
Fraction(int(round(latitude_degree[2] * 100, 0)), 6000),
Fraction(0, 1))
exiv2_longitude = (Fraction(longitude_degree[0] * 60 + longitude_degree[1], 60),
Fraction(int(round(longitude_degree[2] * 100, 0)), 6000),
Fraction(0, 1))
# Exif tags to write
metadata['Exif.GPSInfo.GPSLatitude'] = exiv2_latitude
metadata['Exif.GPSInfo.GPSLatitudeRef'] = latitude_degree[3]
metadata['Exif.GPSInfo.GPSLongitude'] = exiv2_longitude
metadata['Exif.GPSInfo.GPSLongitudeRef'] = longitude_degree[3]
metadata['Exif.Image.GPSTag'] = 654
metadata['Exif.GPSInfo.GPSMapDatum'] = "WGS-84"
metadata['Exif.GPSInfo.GPSVersionID'] = '2 0 0 0'
if not elevation:
metadata['Exif.GPSInfo.GPSAltitude'] = Fraction(elevation)
metadata['Exif.GPSInfo.GPSAltitudeRef'] = '0'
else:
latitude_degree = None
longitude_degree = None
if radiation:
# Set new UserComment
new_comment = 'Radiation ☢ ' + str(radiation) + ' µS/h'
metadata['Exif.Photo.UserComment'] = new_comment
else:
new_comment = None
# Write Exif tags to file, if not in dry-run mode
if dry_run is not True:
metadata.write()
return latitude_degree, longitude_degree, new_comment