diff --git a/import_media.py b/import_media.py new file mode 100644 index 0000000..897d859 --- /dev/null +++ b/import_media.py @@ -0,0 +1,194 @@ +#!/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 + +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.')