From 6624e46164781c066c376ab3c0ab029bbfa8d150 Mon Sep 17 00:00:00 2001 From: Jakub Tymejczyk Date: Tue, 29 Aug 2023 18:03:58 +0200 Subject: [PATCH] add option to use globally unique filenames --- README.md | 2 ++ src/config_parser.py | 10 ++++++++++ src/sync_photos.py | 30 ++++++++++++++++++++++++------ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3fdebfd61..ed73f2bcf 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,8 @@ photos: remove_obsolete: false sync_interval: 500 all_albums: false # Optional, default false. If true preserve album structure. If same photo is in multpile albums creates duplicates on filesystem + # Optional, default false. If true all files have globally unique filenames, it should matter when all_albums is true and one photo is in pultpile album + unique_filenames: false filters: # if all_albums is false list of albums to download, if all_albums is true list of ignored albums # if empty and all_albums is false download all photos to "all" folder. if empty and all_albums is true download all folders diff --git a/src/config_parser.py b/src/config_parser.py index 58a58f707..a942b260b 100644 --- a/src/config_parser.py +++ b/src/config_parser.py @@ -106,6 +106,16 @@ def get_photos_all_albums(config): return download_all +def get_photos_unique_file_names(config): + """Return flag to use unique filenames.""" + unique_filenames = False + config_path = ["photos", "unique_filenames"] + if traverse_config_path(config=config, config_path=config_path): + unique_filenames = get_config_value(config=config, config_path=config_path) + LOGGER.info("Using unique filenames.") + return unique_filenames + + def prepare_root_destination(config): """Prepare root destination.""" LOGGER.debug("Checking root destination ...") diff --git a/src/sync_photos.py b/src/sync_photos.py index e12ec37b8..a2e4e8f22 100644 --- a/src/sync_photos.py +++ b/src/sync_photos.py @@ -22,7 +22,7 @@ def photo_wanted(photo, extensions): return False -def generate_file_name(photo, file_size, destination_path): +def generate_file_name(photo, file_size, destination_path, unique_file_names): """Generate full path to file.""" filename = photo.filename name, extension = filename.rsplit(".", 1) if "." in filename else [filename, ""] @@ -48,7 +48,20 @@ def generate_file_name(photo, file_size, destination_path): os.rename(file_size_path, file_size_id_path) if os.path.isfile(file_size_id_path): os.rename(file_size_id_path, file_size_id_path_norm) - return file_size_id_path_norm + + photo_file_name = file_size_id_path_norm + + if unique_file_names: + album_name = destination_path.split("/")[-1] + file_size_id_album_name_short_path = os.path.join( + destination_path, + f'{"__".join([album_name, name, file_size, base64.urlsafe_b64encode(photo.id.encode()).decode()[2:10]])}.{extension}', + ) + photo_file_name = unicodedata.normalize("NFC", file_size_id_album_name_short_path) + if os.path.isfile(file_size_id_path_norm): + os.rename(file_size_id_path_norm, photo_file_name) + + return photo_file_name def photo_exists(photo, file_size, local_path): @@ -83,10 +96,10 @@ def download_photo(photo, file_size, destination_path): return True -def process_photo(photo, file_size, destination_path, files): +def process_photo(photo, file_size, destination_path, files, unique_file_names): """Process photo details.""" photo_path = generate_file_name( - photo=photo, file_size=file_size, destination_path=destination_path + photo=photo, file_size=file_size, destination_path=destination_path, unique_file_names=unique_file_names ) if file_size not in photo.versions: LOGGER.warning( @@ -101,7 +114,7 @@ def process_photo(photo, file_size, destination_path, files): return True -def sync_album(album, destination_path, file_sizes, extensions=None, files=None): +def sync_album(album, destination_path, file_sizes, extensions=None, files=None, unique_file_names=False): """Sync given album.""" if album is None or destination_path is None or file_sizes is None: return None @@ -110,7 +123,7 @@ def sync_album(album, destination_path, file_sizes, extensions=None, files=None) for photo in album: if photo_wanted(photo, extensions): for file_size in file_sizes: - process_photo(photo, file_size, destination_path, files) + process_photo(photo, file_size, destination_path, files, unique_file_names) else: LOGGER.debug(f"Skipping the unwanted photo {photo.filename}.") for subalbum in album.subalbums: @@ -120,6 +133,7 @@ def sync_album(album, destination_path, file_sizes, extensions=None, files=None) file_sizes, extensions, files, + unique_file_names=unique_file_names, ) return True @@ -145,6 +159,7 @@ def sync_photos(config, photos): filters = config_parser.get_photos_filters(config=config) files = set() download_all = config_parser.get_photos_all_albums(config=config) + unique_file_names = config_parser.get_photos_unique_file_names(config=config) if download_all: for album in photos.albums.keys(): if album in iter(filters["albums"]): @@ -155,6 +170,7 @@ def sync_photos(config, photos): file_sizes=filters["file_sizes"], extensions=filters["extensions"], files=files, + unique_file_names=unique_file_names, ) elif filters["albums"]: for album in iter(filters["albums"]): @@ -164,6 +180,7 @@ def sync_photos(config, photos): file_sizes=filters["file_sizes"], extensions=filters["extensions"], files=files, + unique_file_names=unique_file_names, ) else: sync_album( @@ -172,6 +189,7 @@ def sync_photos(config, photos): file_sizes=filters["file_sizes"], extensions=filters["extensions"], files=files, + unique_file_names=unique_file_names, ) if config_parser.get_photos_remove_obsolete(config=config):