Source code for megadetector.data_management.rename_images

"""

rename_images.py

Copies images from a possibly-nested folder structure to a flat folder structure, including EXIF
timestamps in each filename.  Loosely equivalent to camtrapR's imageRename() function.

"""

#%% Imports and constants

import os
import sys
import argparse

from megadetector.utils.path_utils import \
    find_images, insert_before_extension, parallel_copy_files
from megadetector.data_management.read_exif import \
    ReadExifOptions, read_exif_from_folder


#%% Functions

[docs] def rename_images(input_folder, output_folder, dry_run=False, verbose=False, read_exif_options=None, n_copy_workers=8): """ Copies images from a possibly-nested folder structure to a flat folder structure, including EXIF timestamps in each filename. Loosely equivalent to camtrapR's imageRename() function. Args: input_folder (str): the folder to search for images, always recursive output_folder (str): the folder to which we will copy images; cannot be the same as [input_folder] dry_run (bool, optional): only map images, don't actually copy verbose (bool, optional): enable additional debug output read_exif_options (ReadExifOptions, optional): parameters controlling the reading of EXIF information n_copy_workers (int, optional): number of parallel threads to use for copying Returns: dict: a dict mapping relative filenames in the input folder to relative filenames in the output folder """ assert os.path.isdir(input_folder), 'Input folder {} does not exist'.format( input_folder) if not dry_run: os.makedirs(output_folder,exist_ok=True) # Read exif information if read_exif_options is None: read_exif_options = ReadExifOptions() read_exif_options.tags_to_include = ['DateTime','Model', 'Make','ExifImageWidth', 'ExifImageHeight','DateTimeOriginal'] read_exif_options.verbose = False exif_info = read_exif_from_folder(input_folder=input_folder, output_file=None, options=read_exif_options, filenames=None,recursive=True) print('Read EXIF information for {} images'.format(len(exif_info))) filename_to_exif_info = {info['file_name']:info for info in exif_info} image_files = find_images(input_folder,return_relative_paths=True,convert_slashes=True,recursive=True) for fn in image_files: assert fn in filename_to_exif_info, 'No EXIF info available for {}'.format(fn) input_fn_relative_to_output_fn_relative = {} # fn_relative = image_files[0] for fn_relative in image_files: input_fn_abs = os.path.join(input_folder,fn_relative) image_exif_info = filename_to_exif_info[fn_relative] if 'exif_tags' in image_exif_info: image_exif_info = image_exif_info['exif_tags'] if image_exif_info is None or \ 'DateTimeOriginal' not in image_exif_info or \ image_exif_info['DateTimeOriginal'] is None: dt_tag = 'unknown_datetime' print('Warning: no datetime for {}'.format(fn_relative)) else: dt_tag = str(image_exif_info['DateTimeOriginal']).replace(':','-').replace(' ','_').strip() flat_filename = fn_relative.replace('\\','/').replace('/','_') output_fn_relative = insert_before_extension(flat_filename,dt_tag) input_fn_relative_to_output_fn_relative[fn_relative] = output_fn_relative if not dry_run: input_fn_abs_to_output_fn_abs = {} for input_fn_relative in input_fn_relative_to_output_fn_relative: output_fn_relative = input_fn_relative_to_output_fn_relative[input_fn_relative] input_fn_abs = os.path.join(input_folder,input_fn_relative) output_fn_abs = os.path.join(output_folder,output_fn_relative) input_fn_abs_to_output_fn_abs[input_fn_abs] = output_fn_abs parallel_copy_files(input_file_to_output_file=input_fn_abs_to_output_fn_abs, max_workers=n_copy_workers, use_threads=True, overwrite=True, verbose=verbose) # ...if this is not a dry run return input_fn_relative_to_output_fn_relative
# ...def rename_images() #%% Interactive driver if False: pass #%% Configure options input_folder = r'G:\camera_traps\camera_trap_images\2018.05.04' output_folder = r'g:\temp\rename-test-out' dry_run = False verbose = True read_exif_options = ReadExifOptions() read_exif_options.tags_to_include = ['DateTime','Model','Make', 'ExifImageWidth','ExifImageHeight', 'DateTimeOriginal'] read_exif_options.n_workers = 8 read_exif_options.verbose = verbose n_copy_workers = 8 #%% Programmatic execution input_fn_relative_to_output_fn_relative = rename_images(input_folder, output_folder, dry_run=dry_run, verbose=verbose, read_exif_options=read_exif_options, n_copy_workers=n_copy_workers) #%% Command-line driver
[docs] def main(): # noqa parser = argparse.ArgumentParser( description='Copies images from a possibly-nested folder structure to a flat folder structure, ' + \ 'adding datetime information from EXIF to each filename') parser.add_argument( 'input_folder', type=str, help='The folder to search for images, always recursive') parser.add_argument( 'output_folder', type=str, help='The folder to which we should write the flattened image structure') parser.add_argument( '--dry_run', action='store_true', help="Only map images, don't actually copy") if len(sys.argv[1:]) == 0: parser.print_help() parser.exit() args = parser.parse_args() rename_images(args.input_folder,args.output_folder,dry_run=args.dry_run, verbose=True,read_exif_options=None)
if __name__ == '__main__': main()