195 lines
6.7 KiB
Python
195 lines
6.7 KiB
Python
#!/usr/bin/env python3
|
|
|
|
'''
|
|
Import photos from SD card into folder with todays date + nickname
|
|
Use: importphotos (--jpg|--raw|--both) <nickname of folder (optional)>
|
|
Add script to path
|
|
'''
|
|
|
|
'''
|
|
TODO:
|
|
1. Import configuration from config file
|
|
2. Set raw file extension based on camera specified in configuration
|
|
3. Create destination folders based on concatination of configuration,
|
|
metadata, and event name passed from ARG
|
|
4. Create destination sub-folder based on filetype
|
|
5. Copy files to appropriate folder
|
|
6. Compare files from source
|
|
7. Create 'originals' with copy of files from destination after
|
|
checksum for photos only
|
|
8. Optinally allow specification of a backup location on another disk
|
|
or NAS to ship a 3rd copy to
|
|
9. Optionally cleanup SD only after checksum matching
|
|
10. Every config option has an arg override
|
|
11. Optionally rename file if event name was passed in
|
|
-- STRETCH --
|
|
12. Make a graphical interface
|
|
'''
|
|
|
|
import os
|
|
import sys
|
|
import yaml
|
|
import argparse
|
|
import shutil
|
|
import hashlib
|
|
from datetime import datetime
|
|
import exifread
|
|
|
|
config_file = 'config.yaml'
|
|
|
|
# Read configuration from file
|
|
try:
|
|
with open(config_file, 'r') as f:
|
|
config = yaml.load(f, Loader=yaml.FullLoader)
|
|
except FileNotFoundError:
|
|
print("Configuration file not found: ", config_file)
|
|
print("Copy config.yaml.EXAMPLE to ", config_file, " and update accordingly.")
|
|
|
|
''' Parse Arguments '''
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("-e", "--event", help = "Event Name")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.event:
|
|
event = args.event
|
|
|
|
def md5_hash(f):
|
|
print("calculating md5 for ", f)
|
|
md5 = hashlib.md5(open(f, 'rb').read()).hexdigest()
|
|
return md5
|
|
|
|
def cmp_files(f1,f2):
|
|
print('comparing md5 hashes...')
|
|
return md5_hash(f1) == md5_hash(f2)
|
|
|
|
def file_classification(f):
|
|
print('Classifying media for: ', f)
|
|
for classification in config['file_types']:
|
|
for ext in config['file_types'][classification]:
|
|
if f.lower().endswith(ext):
|
|
c = classification
|
|
return classification
|
|
|
|
def get_capture_date(p, t):
|
|
if t == 'image':
|
|
with open(p, 'rb') as f:
|
|
tags = exifread.process_file(f)
|
|
captured = tags['EXIF DateTimeOriginal']
|
|
year = str(captured).split(' ')[0].split(':')[0]
|
|
month = str(captured).split(' ')[0].split(':')[1]
|
|
day = str(captured).split(' ')[0].split(':')[2]
|
|
else:
|
|
stamp = datetime.fromtimestamp(os.path.getctime(p))
|
|
year = stamp.strftime("%Y")
|
|
month = stamp.strftime("%m")
|
|
day = stamp.strftime("%d")
|
|
return year, month, day
|
|
|
|
def create_folder(f):
|
|
try:
|
|
os.makedirs(f)
|
|
except FileExistsError as exists:
|
|
print()
|
|
|
|
def copy_from_source(p, dest_folder, dest_orig_folder, file):
|
|
if os.path.exists(os.path.join(dest_folder, file)):
|
|
check_match = cmp_files(p, os.path.join(dest_folder, file))
|
|
if check_match == False:
|
|
base, extension = os.path.splitext(file)
|
|
file_name_hash = base + '_' + md5_hash(os.path.join(dest_folder, file)) + extension
|
|
os.rename(os.path.join(dest_folder, file), os.path.join(dest_folder, file_name_hash))
|
|
|
|
shutil.copy(p, dest_folder)
|
|
check_match = cmp_files(p, dest_folder + '/' + file)
|
|
if check_match == False:
|
|
print(f'CRITICAL: md5 hash does not match for {file}')
|
|
print(p, ': ', md5_hash(p))
|
|
print(dest_folder + '/' + file, ': ', md5_hash(dest_folder + '/' + file))
|
|
exit
|
|
|
|
if dest_orig_folder != False:
|
|
shutil.copy(dest_folder + '/' + file, dest_orig_folder)
|
|
check_match = cmp_files(dest_folder + '/' + file, dest_orig_folder + '/' + file)
|
|
if check_match == False:
|
|
print(f'CRITICAL: md5 hash does not match for {file}')
|
|
print(dest_folder + '/' + file, ': ', md5_hash(dest_folder + '/' + file))
|
|
print(dest_orig_folder + '/' + file, ': ', md5_hash(dest_orig_folder + '/' + file))
|
|
exit
|
|
else:
|
|
shutil.copy(p, dest_folder)
|
|
check_match = cmp_files(p, dest_folder + '/' + file)
|
|
if check_match == False:
|
|
print(f'CRITICAL: md5 hash does not match for {file}')
|
|
print(p, ': ', md5_hash(p))
|
|
print(dest_folder + '/' + file, ': ', md5_hash(dest_folder + '/' + file))
|
|
exit
|
|
|
|
if dest_orig_folder != False:
|
|
shutil.copy(dest_folder + '/' + file, dest_orig_folder)
|
|
check_match = cmp_files(dest_folder + '/' + file, dest_orig_folder + '/' + file)
|
|
if check_match == False:
|
|
print(f'CRITICAL: md5 hash does not match for {file}')
|
|
print(dest_folder + '/' + file, ': ', md5_hash(dest_folder + '/' + file))
|
|
print(dest_orig_folder + '/' + file, ': ', md5_hash(dest_orig_folder + '/' + file))
|
|
exit
|
|
|
|
def process_file(p, t, file, ext):
|
|
capture_date = get_capture_date(p, t)
|
|
y = capture_date[0]
|
|
m = capture_date[1]
|
|
d = capture_date[2]
|
|
|
|
if event:
|
|
dest_folder = config['folders']['destination']['base'] + '/' + y + '/' + y + '-' + m + '/' + y + '-' + m + '-' + d + '-' + event
|
|
else:
|
|
dest_folder = config['folders']['destination']['base'] + '/' + y + '/' + y + '-' + m + '/' + y + '-' + m + '-' + d
|
|
|
|
if t == 'image':
|
|
dest_folder = dest_folder + '/photos'
|
|
|
|
if config['store_originals'] == True:
|
|
dest_orig_folder = dest_folder + '/ORIGINALS'
|
|
|
|
if ext in ('jpg', 'jpeg'):
|
|
dest_folder = dest_folder + '/JPG'
|
|
if dest_orig_folder:
|
|
dest_orig_folder = dest_orig_folder + '/JPG'
|
|
else:
|
|
dest_folder = dest_folder + '/RAW'
|
|
if dest_orig_folder:
|
|
dest_orig_folder = dest_orig_folder + '/RAW'
|
|
|
|
elif t == 'video':
|
|
dest_folder = dest_folder + '/VIDEO'
|
|
|
|
elif t == 'audio':
|
|
dest_folder = dest_folder + '/AUDIO'
|
|
|
|
else:
|
|
print(f'WARN: {t} is not a known type and you never should have landed here.')
|
|
|
|
create_folder(dest_folder)
|
|
|
|
try:
|
|
dest_orig_folder
|
|
except NameError:
|
|
dest_orig_folder = False
|
|
else:
|
|
create_folder(dest_orig_folder)
|
|
|
|
copy_from_source(p, dest_folder, dest_orig_folder, file)
|
|
|
|
|
|
def file_list(directory):
|
|
for folder, subfolders, filename in os.walk(directory):
|
|
for t in config['file_types']:
|
|
for ext in config['file_types'][t]:
|
|
for file in filename:
|
|
if file.lower().endswith(ext):
|
|
p = folder + '/' + file
|
|
process_file(p, t, file, ext)
|
|
|
|
file_list(config['folders']['source']['base'])
|
|
print('done.')
|