예제 #1
0
def execute(file_filter_include, file_filter_exclude):

    LocalLibrary.load_library('raw')

    # Result format is:
    # {
    #     "acronym": [list of image paths],
    #         ...
    # }
    result = {}
    albums_seen = {}

    cache = LocalLibrary.cache_raw()
    albums = cache['albums']
    images = cache['images']

    for image in images:

        image_name = image['name']
        image_path = image['path']

        if file_filter_exclude and image_name.find(file_filter_exclude) > -1:
            continue
        if file_filter_include and image_path.find(file_filter_include) < 0:
            continue

        # Need to rerun local library caching
        if not os.path.exists(image_path):
            msg = "Local library not updated.  Please rerun download_local_library again"
            sys.exit(msg)
예제 #2
0
    def upload_album_names(album_names):

        gphoto.init()
        service = GoogleService.service()

        LocalLibrary.load_library('jpg')

        for album_name in album_names:
            Uploader.upload_album_name(service, album_name)
def find(et, album_path_filter, file_filter_include, file_filter_exclude):

    LocalLibrary.load_library('raw')

    # Result structure is of the form:
    #     result = {
    #         "model name": None,
    #             ...
    #     }
    result = {}

    album_path_filter_leaf = None
    if album_path_filter:
        album_path_filter_leaf = os.path.basename(album_path_filter)

    # Walk through each file, split its file name for
    # comparison, and get date shot metadata
    cache = LocalLibrary.cache_raw()
    images = cache['images']
    albums = cache['albums']

    for image in images:
        image_name = image['name']
        image_path = image['path']

        if file_filter_exclude and image_name.find(file_filter_exclude) > -1:
            continue
        if file_filter_include and image_path.find(file_filter_include) < 0:
            continue
        if album_path_filter and not image_path.startswith(album_path_filter):
            continue

        # Get model tag value
        value = None
        try:
            value = et.get_tag("Model", image_path)
        except Exception as e:
            value = None

        # Add the value to result
        result[value] = None

    saveto_filename = "get_unique_models"
    if album_path_filter_leaf:
        saveto_filename += '_d' + album_path_filter_leaf
    if file_filter_include is not None:
        saveto_filename += '_' + file_filter_include

    saveto_filename += '.json'
    saveto = os.path.join(gphoto.cache_dir(), saveto_filename)
    print(f"Saving to: '{saveto}'")

    with open(saveto, "w") as cache_file:
        json.dump(result, cache_file, indent=2)
def execute(album_path_filter):

    LocalLibrary.load_library('raw')

    album_path_filter_leaf = None
    if album_path_filter:
        album_path_filter_leaf = os.path.basename(album_path_filter)

    # The result is going to be of the form
    #     {
    #         "word": none,
    #             ...
    #     }
    result = {}

    # hold sections of cache as local variables
    cache = LocalLibrary.cache_raw()
    albums = cache['albums']
    album_paths = cache['album_paths']
    images = cache['images']
    image_ids = cache['image_ids']    


    # Loop through each album, get caption from it
    # if it follows standard naming convention
    for album in albums:

        # if folder is in the include list then continue
        # Otherwise ignore this album
        album_name = album['name']
        album_path = album['path']
        album_images = album['images']

        # filter out albums
        if album_path_filter and not album_path.startswith(album_path_filter):
            continue

        # Get words from album name
        words = album_name.split(' ')
        for word in words:
            if len(word) <= 3:
                result[word.capitalize()] = None

    saveto_filename = "get_small_words_in_album_names"
    if album_path_filter_leaf:
        saveto_filename += '_d' + album_path_filter_leaf

    saveto_filename += '.json'
    saveto = os.path.join(gphoto.cache_dir(), saveto_filename)
    print(f"Saving to: '{saveto}'")

    with open(saveto, "w") as cache_file:
        json.dump(result, cache_file, indent=2)
def main():
    gphoto.init()
    LocalLibrary.cache_library("p:\\pics", 'raw')
    LocalLibrary.save_library('raw')

    LocalLibrary.cache_library("d:\\picsHres", 'jpg')
    LocalLibrary.save_library('jpg')
예제 #6
0
def main_library_type(et, old_word, new_word, library_type):

    LocalLibrary.load_library(library_type)
    LocalLibraryMetadata.load_library_metadata(library_type)

    library_cache = LocalLibrary.cache(library_type)
    albums = library_cache.get('albums')
    album_paths = library_cache.get('album_paths')
    album_names = library_cache.get('album_names')
    images = library_cache.get('images')
    image_ids = library_cache.get('image_ids')

    library_metadata_cache = LocalLibraryMetadata.cache(library_type)

    for image_path, image_metadata in library_metadata_cache.items():

        desc = image_metadata.get('Description')
        title = image_metadata.get('Title')
        if desc is None:
            desc = title

        if desc is None:
            continue

        if not type(desc) == str:
            continue

        # If misspelled word found in the images description
        # then try to correct it
        if old_word in desc:
            old_desc = desc
            new_desc = desc.replace(old_word, new_word)

            print(f"image='{image_path}'")
            print(f"    old='{old_desc}'")
            print(f"    new='{new_desc}'")

            mime_type = image_metadata.get('MIMEType').split('/')[0]
            is_video = mime_type != 'image'
            ImageUtils.set_caption(et, image_path, new_desc, is_video)

    for album in albums:
        album_path = album.get('path')
        if old_word in album_path:
            new_album_path = album_path.replace(old_word, new_word)
            print(f"album='{album_path}'")
            print(f"    old='{album_path}'")
            print(f"    new='{new_album_path}'")
            if (os.path.exists(album_path)):
                shutil.move(album_path, new_album_path)
def main():
    gphoto.init()

    # Load Local picsHres jpg Library
    LocalLibrary.load_library('jpg')
    local_cache = LocalLibrary.cache_jpg()
    local_albums = local_cache.get('albums')
    local_album_paths = local_cache.get('album_paths')
    local_album_names = local_cache.get('album_names')
    local_images = local_cache.get('images')
    local_image_ids = local_cache.get('image_ids')

    # temp_result is a dict holding image name as key
    # and an array value of image paths
    temp_result = {}

    # Loop through each images, get their leaf names and see
    # if image part with this name already exists
    for local_image in local_images:

        # Add image name and path to the temp_result
        image_name = local_image.get('name')
        image_path = local_image.get('path')

        image_path_list = temp_result.get(image_name)
        if image_path_list is None:
            image_path_list = [image_path]
            temp_result[image_name] = image_path_list
        else:
            image_path_list.append(image_path)

    # Now remove entries for temp_result where there is only one image path
    result = {}
    found = False
    for image_name in temp_result:
        image_path_list = temp_result.get(image_name)
        if len(image_path_list) > 1:
            found = True
            result[image_name] = image_path_list

    print(f"found duplicates = {found}")

    # Save to cache file also
    if found:
        gphoto.save_to_file(result, "find_duplicate_local_image_names.json")
예제 #8
0
  def find():
    """
    This method builds the cache for local raw pics folder,
    traverses it and find image name duplicates
    """
    gphoto.init()
    LocalLibrary.cache_raw_library("p:\\pics")
    LocalLibrary.save_library('raw')

    # The dups dict holds
    #   key: image name
    #   list: list of image paths
    name_to_paths = {}

    # traverse the images list. For each image add its name
    cache = LocalLibrary.cache_raw()
    cache_images = cache['images']
    cache_image_ids = cache['image_ids']

    for image in cache_images:
      imagename = image['name']
      imagepath = image['path']

      if imagename not in name_to_paths:
        name_to_paths[imagename] = [imagepath]
      else:
        name_to_paths[imagename].append(imagepath)

    # review the dups where imagename is holding multiple image paths
    dups = []
    for imagename, imagelist in name_to_paths.items():
      if len(imagelist) > 1:
        dup = {
          'name': imagename,
          'paths': []
        }

        paths = dup['paths']
        for imagepath in imagelist:
          paths.append(imagepath)
        
        dups.append(dup)
    
    return dups
    def map_recursive(self, root, test):
        """
        High-level algorithm:
        1. For each local folder locate the Google album in cache
        2. If Google album does not exist then call 'gphotocli album upload <...path_to_album...>'
            - Add local images to Google Album from the Local album if missing
            - Remove images from Google album that are not in Local album
        """
        # Argument validation
        if not os.path.exists(root):
            logging.error(f"Folder does not exist: ({root})")
            return

        # Remove trailing slash
        slash_char = root[len(root) - 1]
        if slash_char == '/' or slash_char == '\\':
            root = root[:len(root)-1]

        # Get Google API service
        service = GoogleService.service()

        # Initialize Google API and load cache.
        google_cache = GoogleLibrary.cache()
        google_album_ids = google_cache.get('album_ids')
        google_album_titles = google_cache.get('album_titles')

        # Load local library cache
        local_cache = LocalLibrary.cache('jpg')
        local_albums = local_cache.get('albums')

        # Traverse all the sub folders in the cache
        for local_album in local_albums:

            local_album_name = local_album['name']
            local_album_path = local_album['path']

            if not local_album_path.lower().startswith(root.lower()):
                continue

            # If album not in Google Cache, ignore and then error out
            google_album_id = google_album_titles.get(local_album_name)
            google_album = google_album_ids[google_album_id] if google_album_id is not None else None

            if google_album is None:
                logging.error(f"Ignoring album not in Google Cache: '{google_album.get('title')}'")
                continue

            # Do mapping for each Local/Google album
            self.map_album(local_album, google_album, test)
예제 #10
0
    def upload_album_name(service, album_name):

        # Get album in local library by name
        album_cache_jpg = LocalLibrary.cache_jpg()
        album_names = album_cache_jpg['album_names']

        if album_name not in album_names:
            logging.error(f"No local album found by name '{album_name}'")
            return

        local_album_idx = album_names[album_name]
        local_albums = album_cache_jpg['albums']
        local_album = local_albums[local_album_idx]

        # Call common upload method
        Uploader.upload_album(service, local_album)
예제 #11
0
    def upload_recursive(self, root, test):

        # Argument validation
        if not os.path.exists(root):
            logging.error(f"Folder does not exist: ({root})")
            return

        # Remove trailing slash
        slash_char = root[len(root) - 1]
        if slash_char == '/' or slash_char == '\\':
            root = root[:len(root) - 1]

        # Get Google API service
        service = GoogleService.service()

        # Initialize Google API and load cache.
        google_cache = GoogleLibrary.cache()
        google_album_ids = google_cache.get('album_ids')
        google_album_titles = google_cache.get('album_titles')

        # Traverse all the sub folders in the cache
        local_cache = LocalLibrary.cache('jpg')
        local_albums = local_cache.get('albums')

        for local_album in local_albums:

            local_album_name = local_album['name']
            local_album_path = local_album['path']

            if not local_album_path.lower().startswith(root.lower()):
                continue

            # Check if album already in Google Cache
            google_album_id = google_album_titles.get(local_album_name)
            google_album = google_album_ids[
                google_album_id] if google_album_id is not None else None

            if google_album is not None:
                logging.info(
                    f"Album already uploaded: '{google_album.get('title')}'")
                continue

            # Do the actual creating of Google album
            album_response = self.create_shareable_album(
                service=service, album_name=local_album_name, test=test)
            if album_response:
                self.modified = True
    def map_album(self, local_album, google_album, test):

        logging.error(f"Mapping album: '{google_album.get('title')}'")

        # Initialize Google API and load cache.
        google_cache = GoogleLibrary.cache()
        google_album_ids = google_cache.get('album_ids')
        google_album_titles = google_cache.get('album_titles')
        google_image_ids = google_cache.get('image_ids')
        google_album_to_images = google_cache.ge_ids('album_images')

        # Load local library cache
        local_cache = LocalLibrary.cache('jpg')
        local_albums = local_cache.get('albums')
        local_images = local_cache.get('images')
        # local_image_ids = local_cache.get('image_ids')

        # Collect local images belonging to local album
        local_album_image_idxs = local_album.get('images')
        if local_album_image_idxs is None or len(local_album_image_idxs) <= 0:
            logging.info(f"No images found in album '{local_album.get('name')}'")
            return

        # from local album images indices, build local album image list
        local_album_images = {}
        for idx in local_album_image_idxs:
            local_image = local_images[idx]
            local_image_name = local_image.get('name')
            local_album_images[local_image_name] = local_image

        # From google album get images already in it
        google_album_id = google_album.get('id')
        google_album_to_image_ids = google_album_to_images.get(id)
        google_album_images = {}
        for google_image_id in google_album_to_image_ids:
            google_album_image = google_image_ids.get(google_image_id)
            google_album_images[google_image_id] = google_album_image
예제 #13
0
    def upload_album(service, local_album):

        logging.info(
            "------------------------------------------------------------------"
        )
        logging.info(f"  album: '{local_album['path']}'")
        logging.info(
            "------------------------------------------------------------------"
        )

        # Create the album and make it sharable
        # --------------------------------------
        google_album = None
        share_info = None
        try:
            google_album = AlbumAPI.create_album(service, local_album['name'])
            share_info = AlbumAPI.make_album_sharable(service,
                                                      google_album['id'],
                                                      share=True)
        except Exception as e:
            raise

        google_album_id = google_album['id']

        # Loop through all images under the album and upload them
        # --------------------------------------------------------
        local_album_image_idxs = local_album['images']

        local_library_cache = LocalLibrary.cache_jpg()
        local_images = local_library_cache['images']

        for local_image_idx in local_album_image_idxs:

            local_image = local_images[local_image_idx]
            local_image_name = local_image['name']
            local_image_path = local_image['path']
def execute(file_filter_include, file_filter_exclude):

    LocalLibrary.load_library('raw')

    # Result format is:
    # {
    #     "acronym": [list of image paths],
    #         ...
    # }
    result = {}
    albums_seen = {}

    cache = LocalLibrary.cache_raw()
    albums = cache['albums']
    images = cache['images']

    for image in images:

        image_name = image['name']
        image_path = image['path']

        if file_filter_exclude and image_name.find(file_filter_exclude) > -1:
            continue
        if file_filter_include and image_path.find(file_filter_include) < 0:
            continue

        # Need to rerun local library caching
        if not os.path.exists(image_path):
            msg = "Local library not updated.  Please rerun download_local_library again"
            sys.exit(msg)

        # check if file name conforms to yyyymmdd_hhmmss_XXXX
        if len(image_name) < _FILEPREFIX_PATTERN_LEN:
            continue
        image_basename = os.path.splitext(image_name)[0]
        image_name_splits = image_basename.split('_')
        if len(image_name_splits) < 3:
            continue
        image_date = image_name_splits[0]
        image_time = image_name_splits[1]
        image_acronym = '_'.join(image_name_splits[2:])
        if len(image_date) < 8 or len(image_time) < 6:
            continue

        # Get parent album
        album_idx = image['parent']
        album = albums[album_idx]
        album_name = album['name']
        album_path = album['path']

        # if the combination of album name and acronym has already
        # been seen the ignore rest of the images in this album
        album_plus_acronym = album_name + '__' + image_acronym
        if album_plus_acronym in albums_seen:
            continue
        else:
            albums_seen[album_plus_acronym] = None

        # add image acronym and image_path to the result
        if image_acronym not in result:
            image_list = [image_path]
            result[image_acronym] = image_list
        else:
            image_list = result[image_acronym]
            image_list.append(image_path)

    # filter out acronyms where there are no duplicates
    final_result = {}
    for acronym in result.keys():

        image_list = result[acronym]
        if len(image_list) > 1:
            final_result[acronym] = image_list

    saveto_filename = "test_dup_file_acronym"
    if file_filter_include is not None:
        saveto_filename += '_' + file_filter_include

    saveto_filename += '.json'
    saveto = os.path.join(gphoto.cache_dir(), saveto_filename)
    print(f"Saving to: '{saveto}'")

    with open(saveto, "w") as cache_file:
        json.dump(final_result, cache_file, indent=2)
예제 #15
0
 def __init__(self):
     LocalLibrary.load_library('jpg')
     GoogleLibrary.load_library()
     self.modified = False
def main():
    """
    Given a folder tree root like p:\\pics\\2014 loop through
    each album and find its images in Google photos.
    if the images do not have albums then they can be deleted.
    if the images have an album then
        and if the album have more images that the local album images
        and the albums is not shared then the images can be deleted
    """
    if len(sys.argv) < 2:
        logging.error("Too few arguments.  Specify folder pattern")
        return

    # Get arguments
    arg_album_year = sys.argv[1]
    arg_album_pattern = f"\\{arg_album_year}\\"

    LocalLibrary.load_library('jpg')
    local_cache = LocalLibrary.cache_jpg()
    local_albums = local_cache.get('albums')
    local_album_paths = local_cache.get('album_paths')
    local_images = local_cache.get('images')

    GoogleLibrary.load_library()
    google_cache = GoogleLibrary.cache()
    google_album_ids = google_cache['album_ids']
    google_album_titles = google_cache['album_titles']
    google_image_ids = google_cache['image_ids']
    google_image_filenames = google_cache['image_filenames']
    google_album_images = google_cache['album_images']
    google_image_albums = google_cache['image_albums']

    result = []

    # Loop through each local folder under the root tree
    for local_album in local_albums:
        local_album_path = local_album.get('path')

        # filter out the ones that are not under the tree
        if local_album_path.find(arg_album_pattern) == -1:
            continue
        # if not local_album_path.startswith(arg_album_pattern):
        #     continue

        # Add this album to the list
        result_album = {'path': local_album.get('path')}
        result.append(result_album)

        # Get first jpeg image of the local album
        first_local_image = None
        local_album_image_idxs = local_album.get('images')
        for local_album_image_idx in local_album_image_idxs:

            local_image = local_images[local_album_image_idx]
            if local_image.get('mime') == 'image/jpeg':
                first_local_image = local_image
                break

        if first_local_image is None:
            result_album[
                'ERROR'] = f"No jpeg images in local album '{local_album.get('path')}'"
            continue

        result_album['first_image'] = first_local_image['name']

        # Locate this image in Google photos.  Identify the pattern
        # If the image is of the form
        #       YYYYMMDD_hhmmss_nn_AAAA_D800.jpeg
        # or just the actual name
        # First look for the images with actual name, if not found then
        # Look by date time in the filename

        first_google_image_id, pattern_found = find_google_image(
            first_local_image, google_image_ids, google_image_filenames)

        if first_google_image_id is None:
            result_album[
                'WARNING'] = f"First album image not in Google {first_local_image.get('name')}"
            continue

        first_google_image = google_image_ids.get(first_google_image_id)
        result_album['first_google_image'] = {
            'id': first_google_image.get('id'),
            'filename': first_google_image.get('filename'),
            'mine': first_google_image.get('mine'),
            'productUrl': first_google_image.get('productUrl')
        }

        # if the first image part of google album then
        # we need to know if the image is part of a shared album
        google_image_album_list = google_image_albums.get(
            first_google_image_id)
        if google_image_album_list is None or len(
                google_image_album_list) <= 0:
            result_album['NO-GOOGLE-ALBUM'] = True
        else:
            result_image_albums = []
            result_album['HAS-ALBUMS'] = result_image_albums
            for google_image_album_id in google_image_album_list:
                google_album = google_album_ids.get(google_image_album_id)
                result_image_albums.append({
                    'id':
                    google_album.get('id'),
                    'title':
                    google_album.get('title'),
                    'productUrl':
                    google_album.get('productUrl'),
                    'shared':
                    google_album.get('shared')
                })

    gphoto.save_to_file(result,
                        f"can_google_images_be_deleted_{arg_album_year}.json")
예제 #17
0
def check_album_readiness(et, album_path_filter_year, file_filter_include,
                          file_filter_exclude, test_missing_date_shot,
                          test_bad_date_shot, test_filename_FMT,
                          test_Tag_mismatch, test_missing_caption,
                          test_unique_caption, test_missing_caption_year,
                          test_missing_geotags):
    """
    Images should follow the format:
    YYYYMMMDD_HHmmSS....

    If it does not follow this format then that is and
    indication that the file name does not match date shot

    The result is of the form
    {
        "album_path": {
            "reason value": [list of image paths],
                ...
        },
            ...
    }
    """
    print(f"-------------------- args --------------------------")
    print(f"album_path_filter_pattern = {album_path_filter_year}")
    print(f"file_filter_include = {file_filter_include}")
    print(f"file_filter_exclude = {file_filter_exclude}")
    print(f"test_missing_date_shot = {test_missing_date_shot}")
    print(f"test_bad_date_shot = {test_bad_date_shot}")
    print(f"test_filename_FMT = {test_filename_FMT}")
    print(f"test_Tag_mismatch = {test_Tag_mismatch}")
    print(f"test_missing_caption = {test_missing_caption}")
    print(f"test_unique_caption = {test_unique_caption}")
    print(f"test_missing_caption_year = {test_missing_caption_year}")
    print(f"test_missing_geotags = {test_missing_geotags}")
    print(f"----------------------------------------------------")

    unique_caption_reason = "non-unique-captions"
    mismatch_album_image_caption_reason = "mismatch-album-image-captions"
    missing_geotags_reason = "missing-geotags"

    LocalLibrary.load_library('raw')

    result = {}

    album_path_filter_pattern = f"\\{album_path_filter_year}\\"
    print(f"album_path_filter_pattern = {album_path_filter_pattern}")

    # Walk through each file, split its file name for
    # comparison, and get date shot metadata
    cache = LocalLibrary.cache_raw()
    images = cache.get('images')
    albums = cache.get('albums')

    for album in albums:

        album_name = album['name']
        album_path = album['path']

        if album_path_filter_pattern and album_path.find(
                album_path_filter_pattern) < 0:
            continue

        album_splits = album_name.split(' ')
        album_year = album_splits[0].split('-')[0]
        album_caption = album_year + ' ' + ' '.join(album_splits[1:])
        # print(f"album_caption = {album_caption}")

        # Album level results captured here
        # Duplicate captions table.  Every caption of images
        # is hashed here
        unique_caption_dict = {}

        album_images = album['images']
        for image_idx in album_images:
            image = images[image_idx]
            image_name = image['name']
            image_path = image['path']

            if file_filter_exclude and image_name.find(
                    file_filter_exclude) > -1:
                continue
            if file_filter_include and image_path.find(
                    file_filter_include) < 0:
                continue

            image_ext = ImageUtils.get_file_extension(image_name)
            is_video = ImageUtils.is_ext_video(image_ext)

            # Need to rerun local library caching
            if not os.path.exists(image_path):
                msg = "Local library not updated.  Please rerun download_local_library again"
                print(msg)
                sys.exit(msg)

            # Nothing is mismatched yet
            # Each test returns a result as tuple with 3 values:
            #   ("name of the test", True|False if test failed, "extra info")
            mismatched = False
            test_results = []

            # if image date shot does not match images name
            # then add it to the mismatched list.  For PNG use PNG:CreationTime
            tag = None
            if test_missing_date_shot:
                tag = et.get_tag("Exif:DateTimeOriginal", image_path)
                if tag is None or len(tag) <= 0:
                    tag = et.get_tag("Exif:CreateDate", image_path)
                    if tag is None or len(tag) <= 0:
                        tag = et.get_tag("QuickTime:CreateDate", image_path)
                        if tag is None or len(tag) <= 0:
                            mismatched = True
                            test_results.append("missing-date-shot")

            tagsplit = None
            if test_missing_date_shot and test_bad_date_shot and not mismatched:
                tagsplit = tag.split(' ')
                if len(tagsplit) < 2:
                    mismatched = True
                    test_results.append(("bad-date-shot", tag))

            # If image does not follow correct pattern
            # Then add it to album list
            mismatched_filename_format = False
            if test_filename_FMT:
                if len(image_name) < _IMAGE_PATTERN_LEN:
                    mismatched = True
                    mismatched_filename_format = True
                    test_results.append("filename-FMT")

            filedatetime = None
            if test_filename_FMT and not mismatched_filename_format:
                filedatetime = image_name.split('_')
                if len(filedatetime) < 2:
                    mismatched = True
                    mismatched_filename_format = True
                    test_results.append("filename-FMT")

            if test_Tag_mismatch and not mismatched_filename_format:
                file_date = filedatetime[0]
                file_time = filedatetime[1][0:3]
                tag_date = ''.join(tagsplit[0].split(':'))
                tag_time = ''.join(tagsplit[1].split(':'))[0:3]

                if tag_date != file_date or tag_time != file_time:
                    mismatched = True
                    test_results.append(("tag-mismatch", tag))

            # Check missing Caption: check if any of the tags have any value
            caption = None
            if test_missing_caption:
                caption = ImageUtils.get_caption(et, image_path, is_video)
                if caption is None or len(caption) <= 0:
                    mismatched = True
                    test_results.append("missing-caption")
                elif test_unique_caption:
                    year = None
                    if len(caption) > 4:
                        year = caption[0:4]
                        if not year.isdecimal():
                            unique_caption_dict[caption] = None

            # Check missing Caption year
            if test_missing_caption_year and caption is not None:
                if not test_missing_caption:
                    caption = ImageUtils.get_caption(et, image_path, is_video)
                if not test_missing_caption and (caption is None
                                                 or len(caption) <= 0):
                    mismatched = True
                    test_results.append("missing-caption")
                elif not test_missing_caption and len(caption) < 5:
                    mismatched = True
                    test_results.append("missing-caption")
                else:
                    caption_year = caption[0:4]
                    if not caption_year.isdecimal():
                        mismatched = True
                        test_results.append(("missing-caption-year", caption))

            # If caption has full date then report it
            if caption is not None and len(caption) > 11:
                caption_year = caption[0:4]
                first_dash = caption[4]
                second_dash = caption[7]
                if caption_year.isdecimal(
                ) and first_dash == '-' and second_dash == '-':
                    mismatched = True
                    test_results.append(("full-date-prefix", caption))

            # If caption different from album then report it
            if caption is not None and caption != album_caption:
                unique_caption_dict[caption] = None

            # Test missing geotags
            if test_missing_geotags and not is_video:
                geotags = None
                try:
                    geotags = et.get_tags([
                        "GPSLatitude", "GPSLongitude", "GPSLatitudeRef",
                        "GPSLongitudeRef"
                    ], image_path)
                except Exception as e:
                    geotags = None

                if geotags is None or len(geotags) < 4:
                    mismatched = True
                    test_results.append(missing_geotags_reason)

            if mismatched:
                for test_result in test_results:
                    mismatch_reason = None
                    mismatch_desc = None
                    if type(test_result) is not tuple:
                        mismatch_reason = test_result
                    else:
                        mismatch_reason = test_result[0]
                        mismatch_desc = test_result[1]

                    reason_result = None
                    if mismatch_reason not in result:
                        reason_result = {}
                        result[mismatch_reason] = reason_result
                    else:
                        reason_result = result[mismatch_reason]

                    album_result = None
                    if album_path not in reason_result:
                        album_result = []
                        reason_result[album_path] = album_result
                    else:
                        album_result = reason_result[album_path]

                    if type(test_result) is not tuple:
                        album_result.append(image_path)
                    else:
                        album_result.append((mismatch_desc, image_path))

        # add duplicate caption results
        if len(unique_caption_dict) > 1:
            unique_caption_result = None
            if unique_caption_reason not in result:
                unique_caption_result = {}
                result[unique_caption_reason] = unique_caption_result
            else:
                unique_caption_result = result[unique_caption_reason]

            unique_caption_result[album_path] = list(
                unique_caption_dict.keys())

        # If caption is same for all images but diff from album then report it
        if len(unique_caption_dict) == 1:
            image_caption = str(next(iter(unique_caption_dict)))

            # Strip the month and day from the album name
            splits = album_name.split(' ')
            album_date = splits[0]
            album_desc = splits[1:]
            album_year = album_date[0:4]
            album_caption = album_year + ' ' + ' '.join(album_desc)

            if album_caption != image_caption:
                mismatch_album_image_caption_result = None
                if mismatch_album_image_caption_reason not in result:
                    mismatch_album_image_caption_result = {}
                    result[
                        mismatch_album_image_caption_reason] = mismatch_album_image_caption_result
                else:
                    mismatch_album_image_caption_result = result[
                        mismatch_album_image_caption_reason]

                mismatch_album_image_caption_result[album_path] = {
                    'album_caption': album_caption,
                    'image_caption': image_caption
                }

    saveto_filename = "check_album_readiness"
    if album_path_filter_year:
        saveto_filename += '_d' + album_path_filter_year
    if file_filter_include is not None:
        saveto_filename += '_' + file_filter_include

    if test_missing_date_shot or test_bad_date_shot:
        saveto_filename += "_dtshot"
    if test_filename_FMT:
        saveto_filename += "_ffmt"
    if test_Tag_mismatch:
        saveto_filename += "_Tagmm"
    if test_missing_caption:
        saveto_filename += "_miscap"
    if test_unique_caption:
        saveto_filename += "_dupcap"

    saveto_filename += '.json'
    saveto = os.path.join(gphoto.cache_dir(), saveto_filename)
    print(f"Saving to: '{saveto}'")

    with open(saveto, "w") as cache_file:
        json.dump(result, cache_file, indent=2)
def main_with_exiftool(et, file_filter_pattern):
    """
    If date shot is missing in iPhone file then get it from
    the filename of the format is like: "2015-02-17 19.30.28.jpg"
    then update the dateshot from the filename
    """
    LocalLibrary.load_library('raw')

    result = {}

    # Walk through each file, split its file name for
    # comparison, and get date shot metadata
    cache = LocalLibrary.cache_raw()
    images = cache['images']
    albums = cache['albums']

    for image in images:
        image_name = image['name']
        image_path = image['path']
        image_ext = ImageUtils.get_file_extension(image_name)

        # if filter is specified and does not match to the file path
        # then ignore the file
        if file_filter_pattern and image_path.find(file_filter_pattern) < 0:
            continue

        if not os.path.exists(image_path):
            continue

        is_video = image_ext in gphoto.core.VIDEO_EXTENSIONS

        # If the file has dateshot then ignore it
        tag = None
        if not is_video:
            tag = et.get_Tag("Exif:DateTimeOriginal", image_path)
        else:
            tag = et.get_Tag("QuickTime:CreateDate", image_path)
        if tag is not None:
            continue

        # at this point dateshot is missing
        # parse the file and check for format as "2015-02-17 19.30.28.jpg"
        splits = image_name.split(' ')
        if len(splits) < 2:
            continue
        file_date = splits[0]
        file_time = splits[1]
        if file_date is None or file_time is None:
            continue

        file_date_splits = file_date.split('-')
        if len(file_date_splits) < 3:
            continue

        file_time_splits = file_time.split('.')
        if len(file_time_splits) < 4:
            continue

        dateshot = ':'.join(file_date_splits) + ' ' + ':'.join(
            file_time_splits[0:3])

        # cmd = "\"-" + ImageUtils._TagIPTCObjectName + '=' + dateshot + '"'
        # cmd += "\" -" + ImageUtils._TagIPTCCaptionAbstract + '=' + dateshot + '"'
        # cmd += "\" -" + ImageUtils._TagExifImageDescription + '=' + dateshot + '"'
        # cmd += "\" -" + ImageUtils._TagXmpDescription + '=' + dateshot + '"'

        # ret = subprocess.run(["exiftool", f"-EXIF:DateTimeOriginal={dateshot}", "-EXIF:CreateDate={dateshot}", "-overwrite_original", "-P", image_path])

        ret = None
        if not is_video:
            ret = subprocess.run([
                "exiftool", f"-EXIF:DateTimeOriginal={dateshot}",
                "-overwrite_original", image_path
            ])
            print(f"Image Date Set: {image_path}")
        else:
            ret = subprocess.run([
                "exiftool", f"-QuickTime:CreateDate={dateshot}",
                "-overwrite_original", "-ext", "mov", "-ext", "mp4", image_path
            ])
            print(f"Video Date Set: {image_path}")

        print(f"retcode: {ret.returncode}, {dateshot}, {image_path}")
예제 #19
0
def main():
    """
    Collect all the images with the date shot of the given year.
    Check if they follow the YYYYMMDD_HHMMDD_.... format
    If all follow this format then images of the whole year can be deleted
        in one shot.
    Otherwise list out the odd image months from the date shot as culprits.
    For each of these images see in the local folder album what to do.
    """
    if len(sys.argv) < 2:
        logging.error("Too few arguments.  Specify date shot year")
        return

    # Get arguments
    args_year = sys.argv[1]

    LocalLibrary.load_library('jpg')
    local_cache = LocalLibrary.cache_jpg()
    local_albums = local_cache.get('albums')
    local_album_paths = local_cache.get('album_paths')
    local_images = local_cache.get('images')


    GoogleLibrary.load_library()
    google_cache = GoogleLibrary.cache()
    google_album_ids = google_cache['album_ids']
    google_album_titles = google_cache['album_titles']
    google_image_ids = google_cache['image_ids']
    google_image_filenames = google_cache['image_filenames']
    google_album_images = google_cache['album_images']
    google_image_albums = google_cache['image_albums']

    google_images_with_missing_dateshot = []
    google_images_missing_locally = []
    local_images_with_non_standandard_filename = []
    google_images_with_non_standandard_filename = []
    google_images_with_arg_year = []
    google_images_by_datetime = {}
    local_images_by_datetime = {}

    result = {
        'google_images_with_missing_dateshot': google_images_with_missing_dateshot,
        'google_images_missing_locally': google_images_missing_locally,
        'local_images_with_non_standandard_filename': local_images_with_non_standandard_filename,
        'google_images_with_non_standandard_filename': google_images_with_non_standandard_filename,
        'google_images_with_arg_year': google_images_with_arg_year,
        'google_images_by_datetime': google_images_by_datetime,
        'local_images_by_datetime': local_images_by_datetime
    }

    # First collect all google images in the given year
    for google_image_id, google_image in google_image_ids.items():
        mediaMetadata = google_image.get('mediaMetadata')
        if mediaMetadata is None:
            google_images_with_missing_dateshot.append(google_image)

        else:
            creationTime = mediaMetadata.get('creationTime')
            if creationTime is None:
                google_images_with_missing_dateshot.append(google_image)

            else:
                # Date shot is of the format "2021-02-15T20:29:52Z
                # Extract the year from it
                image_year = creationTime.split('-')[0]
                if image_year == args_year:
                    google_images_with_arg_year.append(google_image)

    # If the google images does not have format YYYYMMDD_HHMMSS_...
    # then there is an issue
    for google_image in google_images_with_arg_year:
        filename = google_image.get('filename')
        splits = filename.split('_')
        if len(splits) < 3:
            google_images_with_non_standandard_filename.append(google_image)
        else:
            image_date = splits[0]
            image_time = splits[1]
            if len(image_date) < 8 or not image_date.isdecimal():
                google_images_with_non_standandard_filename.append(google_image)
            elif len(image_time) < 6 or not image_time.isdecimal():
                google_images_with_non_standandard_filename.append(google_image)
            else:
                pass
                # image_datetime = image_date + '_' + image_time
                # google_images_by_datetime[image_datetime] = {
                #     'filename': google_image.get('filename'),
                #     'productUrl': google_image.get('productUrl')
                # }

    # now make a list of all the local images in the year specified
    # and add them to the local_images_by_dateshot.
    pattern = f"\\{args_year}\\"
    for local_album_idx, local_album in enumerate(local_albums):
        album_path = local_album.get('path')
        if pattern not in album_path:
            continue

        album_image_idxs = local_album.get('images')
        for album_image_idx in album_image_idxs:
            local_image = local_images[album_image_idx]
            local_image_name = local_image.get('name')
            splits = local_image_name.split('_')
            if len(splits) < 3:
                local_images_with_non_standandard_filename.append(local_image.get('path'))

            image_date = splits[0]
            image_time = splits[1]
            if len(image_date) < 8 or not image_date.isdecimal():
                local_images_with_non_standandard_filename.append(local_image.get('path'))
            elif len(image_time) < 6 or not image_time.isdecimal():
                local_images_with_non_standandard_filename.append(local_image.get('path'))
            else:
                image_datetime = image_date + '_' + image_time
                local_images_by_datetime[image_datetime] = {
                    'filename': local_image.get('name'),
                    'path': local_image.get('path')
                }


    # Now traverse through all the google images with date shot
    # and locate them in local images
    # If not found then error
    for datetime, google_image in google_images_by_datetime.items():
        local_image = local_images_by_datetime.get(datetime)
        if local_image is None:
            google_images_missing_locally.append(google_image)

    bn = os.path.basename(args_year)
    gphoto.save_to_file(result, f"can_google_images_be_deleted_by_year_{bn}.json")
def do_work(et, google_image_filter, album_folder_path, list_only):

    # Find folder album in the database
    LocalLibrary.load_library('raw')
    local_library_cache = LocalLibrary.cache_raw()
    images = local_library_cache['images']
    albums = local_library_cache['albums']
    album_paths = local_library_cache['album_paths']

    album_idx = album_paths[album_folder_path]
    album = albums[album_idx]
    local_album_path = album['path']

    print(f"[INFO]: Found album '{local_album_path}'")

    # Collect list of local album files
    local_files_results = []
    local_album_images = album['images']
    for image_idx in local_album_images:
        image = images[image_idx]
        image_name = image['name']
        image_path = image['path']
        local_files_results.append(image_path)

    sorted(local_files_results)
    util.pprint(local_files_results)
    print(f"[INFO] Local files count '{len(local_files_results)}'")

    # Collect a list of images from google photos
    # Each element in this list will be an object:
    #     {'path': 'image path', 'caption': 'images caption...'}
    google_images_results = []
    gphoto.init()
    GoogleImages.load_images()
    google_image_cache = GoogleImages.cache()
    google_images = google_image_cache['list']
    for google_image in google_images:
        image_name = google_image['filename']
        if image_name.find(google_image_filter) < 0:
            continue
        image_desc = google_image['description']

        google_images_results.append((image_name, image_desc))

    google_images_results = sorted(google_images_results,
                                   key=lambda record: record[0])
    util.pprint(google_images_results)
    print(f"[INFO] Google files count '{len(google_images_results)}'")

    # Perform basic validations
    # If counts are not the same then error out
    if len(local_files_results) != len(google_images_results):
        print(
            f"[ERROR]: Count mismatch local: '{len(local_files_results)}', google: '{len(google_images_results)}'.  Aborting"
        )

    # Now loop through the list of folder images, get its
    # equivalent caption from the corresponding google image
    if list_only:
        return

    for image_idx, local_image_path in enumerate(local_files_results):
        desc = google_images_results[image_idx][1]

        # Get image extension and identify it as an image or video
        image_name = os.path.basename(local_image_path)
        image_ext = ImageUtils.get_file_extension(image_name)
        is_video = ImageUtils.is_ext_video(image_ext)

        # Set the caption now
        ImageUtils.set_caption(et, local_image_path, desc, is_video)
예제 #21
0
def main():
    gphoto.init()

    # Load Google Library
    GoogleLibrary.load_library()
    google_cache = GoogleLibrary.cache()
    google_album_ids = google_cache['album_ids']
    google_album_titles = google_cache['album_titles']

    google_image_ids = google_cache['image_ids']
    google_image_filenames = google_cache['image_filenames']

    google_album_images = google_cache['album_images']
    google_image_albums = google_cache['image_albums']

    # Load Local picsHres jpg Library
    LocalLibrary.load_library('jpg')
    local_cache = LocalLibrary.cache_jpg()
    local_albums = local_cache.get('albums')
    local_album_paths = local_cache.get('album_paths')
    local_album_names = local_cache.get('album_names')
    local_images = local_cache.get('images')
    local_image_ids = local_cache.get('image_ids')
    local_image_names = local_cache.get('image_names')

    # Initialize the result
    missing_images_with_album_reason = "MISSING_IMAGES_WITH_ALBUM"
    missing_images_with_no_album_reason = "MISSING_IMAGES_WITH_NO_ALBUM"
    image_exist_locally_reason = "IMAGE_EXIST_LOCALLY"

    result_missing_images_with_album = {}
    result_missing_images_with_no_album = []
    result_image_exist_locally = []
    result = {
        missing_images_with_album_reason: result_missing_images_with_album,
        missing_images_with_no_album_reason: result_missing_images_with_no_album,
        image_exist_locally_reason: result_image_exist_locally
    }

    # Walk through each Google images that begins with PFILMmmm_nnn.jpg
    for google_image_id in google_image_ids:
        google_image = google_image_ids[google_image_id]

        # Ignore images not begining with "PFILM"
        image_name = google_image.get('filename')
        if image_name is not None and not image_name.startswith("PFILM"):
            continue

        # Check for image exist locally
        local_image_idx = local_image_names.get(image_name)
        if local_image_idx is not None:
            local_image = local_images[local_image_idx]
            result_image_exist_locally.append(local_image.get('path'))
            continue

        # We now know that the image is missing locally
        # No figure out if this images does not have an album parent
        google_albums_of_this_image = google_image_albums.get(google_image_id)
        if google_albums_of_this_image is not None:

            # Images does have parent albums
            # add first album to the result first if not already done
            google_album_idx = None
            for idx in google_albums_of_this_image:
                google_album_idx = idx
                break

            google_album = google_album[google_album_idx]
            google_album_id = google_album.get('id')
            result_album = result.get(google_album_id)

            # If album not in result then add the album
            missing_images_with_album = None
            if result_album is None:

                missing_images_with_album = []
                result_album = {
                    'id': google_album_id,
                    'title': google_album.get('title'),
                    'images': missing_images_with_album
                }
                result_missing_images_with_album[google_album_id] = result_album


            # Add missing image to parent album result
            missing_images_with_album.append({
                'id': google_image_id,
                'filename': image_name,
                'productUrl': google_image['productUrl']
            })

        # Google image is missing locally and has no parent album
        else:
            result_missing_images_with_no_album.append({
                    'id': google_image_id,
                    'filename': image_name,
                    'productUrl': google_image['productUrl']
                })

    # Save to cache file also
    gphoto.save_to_file(result, "can_PFILMs_be_deleted.json")