#!/usr/bin/env python3 ''' Import photos from SD card into folder with todays date + nickname Use: importphotos (--jpg|--raw|--both) 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 import ffmpeg 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) stamp = datetime.strptime(str(tags['EXIF DateTimeOriginal']), '%Y:%m:%d %H:%M:%S') elif t == 'video': stamp = datetime.strptime(ffmpeg.probe(p)['format']['tags']['creation_time'], '%Y-%m-%dT%H:%M:%S.%f%z') elif t == 'audio': stamp = datetime.strptime(ffmpeg.probe(p)['format']['tags']['date'], '%Y-%m-%d') 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: print(f'Found duplicate for {p}, renaming destination with md5 appended.') 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 # Blindly assume md5 check has passed... if config['cleanup_sd'] == True: os.remove(p) 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.')