Repository: mandarons/icloud-drive-docker
Pull request #156 (2023-10-05 19:18:02)

add support for shared library
Requires https://github.com/mandaron/icloudpy/pull/33

There's no support for albums in shared libraries - it looks like problem on API side. Same info is on iCloud.com in browser.

To use shared library there's new config option `library`. Value is id of library or key can be omitted if default.

Due to fact that albums are not supported only `all_albums: false` works.


Patch:
diff --git a/README.md b/README.md
index ce1fdf9a..cdb3ec8f 100644
--- a/README.md
+++ b/README.md
@@ -117,6 +117,11 @@ photos:
   all_albums: false # Optional, default false. If true preserve album structure. If same photo is in multpile albums creates duplicates on filesystem
   folder_format: "%Y/%m" # optional, if set put photos in subfolders according to format. Format cheatsheet - https://strftime.org
   filters:
+    # List of libraries to download. If omitted (default), photos from all libraries (own and shared) are downloaded. If included, photos only
+    # from the listed libraries are downloaded.
+    # libraries:
+    #   - PrimarySync # Name of the own library
+
     # if all_albums is false - albums list is used as filter-in, if all_albums is true - albums list is used as filter-out
     # if albums list is empty and all_albums is false download all photos to "all" folder. if empty and all_albums is true download all folders
     albums:
diff --git a/config.yaml b/config.yaml
index 290d1228..c796a09f 100644
--- a/config.yaml
+++ b/config.yaml
@@ -48,6 +48,11 @@ photos:
   all_albums: false # Optional, default false. If true preserve album structure. If same photo is in multpile albums creates duplicates on filesystem
   # folder_format: "%Y/%m" # optional, if set put photos in subfolders according to format. Format cheatsheet - https://strftime.org
   filters:
+    # List of libraries to download. If omitted (default), photos from all libraries (own and shared) are downloaded. If included, photos only
+    # from the listed libraries are downloaded.
+    # libraries:
+    #   - PrimarySync # Name of the own library
+
     # if all_albums is false - albums list is used as filter-in, if all_albums is true - albums list is used as filter-out
     # if albums list is empty and all_albums is false download all photos to "all" folder. if empty and all_albums is true download all folders
     albums:
diff --git a/src/config_parser.py b/src/config_parser.py
index 3de28131..e24a65ae 100644
--- a/src/config_parser.py
+++ b/src/config_parser.py
@@ -280,59 +280,86 @@ def get_photos_remove_obsolete(config):
 
 def get_photos_filters(config):
     """Return photos filters from config."""
-    photos_filters = {"albums": None, "file_sizes": ["original"], "extensions": None}
+    photos_filters = {
+        "libraries": None,
+        "albums": None,
+        "file_sizes": ["original"],
+        "extensions": None,
+    }
     valid_file_sizes = ["original", "medium", "thumb"]
     config_path = ["photos", "filters"]
+
+    # Check for filters
     if not traverse_config_path(config=config, config_path=config_path):
         LOGGER.warning(
-            f"{config_path_to_string(config_path=config_path)} not found. Downloading all albums with original size ..."
+            f"{config_path_to_string(config_path=config_path)} not found. \
+                Downloading all libraries and albums with original size ..."
+        )
+        return photos_filters
+
+    # Parse libraries
+    config_path.append("libraries")
+    if (
+        not traverse_config_path(config=config, config_path=config_path)
+        or not get_config_value(config=config, config_path=config_path)
+        or len(get_config_value(config=config, config_path=config_path)) == 0
+    ):
+        LOGGER.warning(
+            f"{config_path_to_string(config_path=config_path)} not found. Downloading all libraries ..."
         )
     else:
-        config_path.append("albums")
-        if (
-            not traverse_config_path(config=config, config_path=config_path)
-            or not get_config_value(config=config, config_path=config_path)
-            or len(get_config_value(config=config, config_path=config_path)) == 0
-        ):
-            LOGGER.warning(
-                f"{config_path_to_string(config_path=config_path)} not found. Downloading all albums ..."
-            )
-        else:
-            photos_filters["albums"] = get_config_value(
-                config=config, config_path=config_path
-            )
+        photos_filters["libraries"] = get_config_value(
+            config=config, config_path=config_path
+        )
 
-        config_path[2] = "file_sizes"
-        if not traverse_config_path(config=config, config_path=config_path):
-            LOGGER.warning(
-                f"{config_path_to_string(config_path=config_path)} not found. Downloading original size photos ..."
-            )
-        else:
-            file_sizes = get_config_value(config=config, config_path=config_path)
-            for file_size in file_sizes:
-                if file_size not in valid_file_sizes:
-                    LOGGER.warning(
-                        f"Skipping the invalid file size {file_size}, "
-                        + f"valid file sizes are {','.join(valid_file_sizes)}."
-                    )
-                    file_sizes.remove(file_size)
-                    if len(file_sizes) == 0:
-                        file_sizes = ["original"]
-            photos_filters["file_sizes"] = file_sizes
-
-        config_path[2] = "extensions"
-        if (
-            not traverse_config_path(config=config, config_path=config_path)
-            or not get_config_value(config=config, config_path=config_path)
-            or len(get_config_value(config=config, config_path=config_path)) == 0
-        ):
-            LOGGER.warning(
-                f"{config_path_to_string(config_path=config_path)} not found. Downloading all extensions ..."
-            )
-        else:
-            photos_filters["extensions"] = get_config_value(
-                config=config, config_path=config_path
-            )
+    # Parse albums
+    config_path[2] = "albums"
+    if (
+        not traverse_config_path(config=config, config_path=config_path)
+        or not get_config_value(config=config, config_path=config_path)
+        or len(get_config_value(config=config, config_path=config_path)) == 0
+    ):
+        LOGGER.warning(
+            f"{config_path_to_string(config_path=config_path)} not found. Downloading all albums ..."
+        )
+    else:
+        photos_filters["albums"] = get_config_value(
+            config=config, config_path=config_path
+        )
+
+    # Parse file sizes
+    config_path[2] = "file_sizes"
+    if not traverse_config_path(config=config, config_path=config_path):
+        LOGGER.warning(
+            f"{config_path_to_string(config_path=config_path)} not found. Downloading original size photos ..."
+        )
+    else:
+        file_sizes = get_config_value(config=config, config_path=config_path)
+        for file_size in file_sizes:
+            if file_size not in valid_file_sizes:
+                LOGGER.warning(
+                    f"Skipping the invalid file size {file_size}, "
+                    + f"valid file sizes are {','.join(valid_file_sizes)}."
+                )
+                file_sizes.remove(file_size)
+                if len(file_sizes) == 0:
+                    file_sizes = ["original"]
+        photos_filters["file_sizes"] = file_sizes
+
+    # Parse extensions
+    config_path[2] = "extensions"
+    if (
+        not traverse_config_path(config=config, config_path=config_path)
+        or not get_config_value(config=config, config_path=config_path)
+        or len(get_config_value(config=config, config_path=config_path)) == 0
+    ):
+        LOGGER.warning(
+            f"{config_path_to_string(config_path=config_path)} not found. Downloading all extensions ..."
+        )
+    else:
+        photos_filters["extensions"] = get_config_value(
+            config=config, config_path=config_path
+        )
 
     return photos_filters
 
diff --git a/src/sync_photos.py b/src/sync_photos.py
index 5dd72043..56a0e3f6 100644
--- a/src/sync_photos.py
+++ b/src/sync_photos.py
@@ -162,38 +162,42 @@ 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)
+    libraries = (
+        filters["libraries"] if filters["libraries"] is not None else photos.libraries
+    )
     folder_format = config_parser.get_photos_folder_format(config=config)
-    if download_all:
-        for album in photos.albums.keys():
-            if filters["albums"] and album in iter(filters["albums"]):
-                continue
-            sync_album(
-                album=photos.albums[album],
-                destination_path=os.path.join(destination_path, album),
-                file_sizes=filters["file_sizes"],
-                extensions=filters["extensions"],
-                files=files,
-                folder_format=folder_format,
-            )
-    elif filters["albums"]:
-        for album in iter(filters["albums"]):
+    for library in libraries:
+        if download_all and library == "PrimarySync":
+            for album in photos.libraries[library].albums.keys():
+                if filters["albums"] and album in iter(filters["albums"]):
+                    continue
+                sync_album(
+                    album=photos.libraries[library].albums[album],
+                    destination_path=os.path.join(destination_path, album),
+                    file_sizes=filters["file_sizes"],
+                    extensions=filters["extensions"],
+                    files=files,
+                    folder_format=folder_format,
+                )
+        elif filters["albums"] and library == "PrimarySync":
+            for album in iter(filters["albums"]):
+                sync_album(
+                    album=photos.libraries[library].albums[album],
+                    destination_path=os.path.join(destination_path, album),
+                    file_sizes=filters["file_sizes"],
+                    extensions=filters["extensions"],
+                    files=files,
+                    folder_format=folder_format,
+                )
+        else:
             sync_album(
-                album=photos.albums[album],
-                destination_path=os.path.join(destination_path, album),
+                album=photos.libraries[library].all,
+                destination_path=os.path.join(destination_path, "all"),
                 file_sizes=filters["file_sizes"],
                 extensions=filters["extensions"],
                 files=files,
                 folder_format=folder_format,
             )
-    else:
-        sync_album(
-            album=photos.all,
-            destination_path=os.path.join(destination_path, "all"),
-            file_sizes=filters["file_sizes"],
-            extensions=filters["extensions"],
-            files=files,
-            folder_format=folder_format,
-        )
 
     if config_parser.get_photos_remove_obsolete(config=config):
         remove_obsolete(destination_path, files)


Test patch:
diff --git a/tests/data/__init__.py b/tests/data/__init__.py
index bb394722..6b8ef705 100644
--- a/tests/data/__init__.py
+++ b/tests/data/__init__.py
@@ -54,6 +54,35 @@ CLIENT_ID = "client_id"
 # Data
 AUTH_OK = {"authType": "hsa2"}
 
+ZONES_LIST_WORKING = {
+    "zones": [
+        {
+            "zoneID": {
+                "zoneName": "PrimarySync",
+                "ownerRecordName": "_fvhhqlzef1uvsgxnrw119mylkpjut1a0",
+                "zoneType": "REGULAR_CUSTOM_ZONE",
+            },
+            "syncToken": "HwoECJGaGRgAIhYI/ZL516KyxaXfARDm2sbu7KeQiZABKAA=",
+            "atomic": True,
+            "isEligibleForZoneShare": True,
+            "isEligibleForHierarchicalShare": True,
+            "ttl": 0,
+            "disableZoneDuringTtl": True,
+        },
+        {
+            "zoneID": {
+                "zoneName": "SharedSync-9DD9B767-9F30-4D6F-B658-F17DBA16D107",
+                "ownerRecordName": "_fvhhqlzef1uvsgxnrw119mylkpjut1a0",
+                "zoneType": "REGULAR_CUSTOM_ZONE",
+            },
+            "syncToken": "HwoECLiXBRgAIhUIs5Xxntqrrr9UEOLj2/q+geCjpAEoAA==",
+            "atomic": True,
+            "isEligibleForZoneShare": True,
+            "isEligibleForHierarchicalShare": False,
+        },
+    ]
+}
+
 LOGIN_WORKING = {
     "dsInfo": {
         "lastName": LAST_NAME,
@@ -3661,7 +3690,7 @@ PHOTOS_GET_CURRENT_SYNC_TOKEN_WORKING = {
             "deleted": False,
             "zoneID": {
                 "zoneName": "PrimarySync",
-                "ownerRecordName": "_0b5c3c201b3a7f1daac8ff7e7fbc0c35",
+                "ownerRecordName": "_fvhhqlzef1uvsgxnrw119mylkpjut1a0",
                 "zoneType": "REGULAR_CUSTOM_ZONE",
             },
         }
@@ -3855,6 +3884,8 @@ class ICloudPySessionMock(base.ICloudPySession):
 
         # Photos
         if "com.apple.photos.cloud" in url:
+            if url.endswith("zones/list"):
+                return ResponseMock(ZONES_LIST_WORKING)
             if "query?remapEnums=True&getCurrentSyncToken=True" in url:
                 if data.get("query").get("recordType") == "CheckIndexingState":
                     return ResponseMock(
@@ -3962,12 +3993,37 @@ class ICloudPySessionMock(base.ICloudPySession):
                                 "query?remapEnums=True&getCurrentSyncToken=True"
                             ][7]["response"]
                         )
+                    if (
+                        "zoneID" in data
+                        and data.get("zoneID").get("zoneName")
+                        == "SharedSync-9DD9B767-9F30-4D6F-B658-F17DBA16D107"
+                    ):
+                        return ResponseMock(
+                            photos_data.DATA[
+                                "query?remapEnums=True&getCurrentSyncToken=True"
+                            ][8]["response"]
+                        )
 
                     return ResponseMock(
                         photos_data.DATA[
                             "query?remapEnums=True&getCurrentSyncToken=True"
                         ][1]["response"]
                     )
+                if (
+                    data.get("query").get("recordType")
+                    == "CPLAssetAndMasterByAddedDate"
+                ):
+                    if data.get("query").get("filterBy")[0]["fieldValue"]["value"] == 0:
+                        return ResponseMock(
+                            photos_data.DATA[
+                                "query?remapEnums=True&getCurrentSyncToken=True"
+                            ][9]["response"]
+                        )
+                    return ResponseMock(
+                        photos_data.DATA[
+                            "query?remapEnums=True&getCurrentSyncToken=True"
+                        ][8]["response"]
+                    )
                 if (
                     data.get("query").get("recordType")
                     == "CPLContainerRelationLiveByAssetDate"
@@ -4013,6 +4069,9 @@ class ICloudPySessionMock(base.ICloudPySession):
             # IMG_3148.JPG another device
             or "https://cvws.icloud-content.com/B/ATTRy6p-Q3U1HqcF6BUKrrOMnjvoATqG89bMsXhtmMRMw009uhyJc_Kh"
             in url
+            # IMG_5513.HEIC Shared Library
+            or "https://cvws.icloud-content.com/B/AQDN6auXvelQyb_btBqkNNjA97E2AZ_h3_ZBuSDV7J1SfMKpllmP-FGN"
+            in url
         ):
             return ResponseMock(
                 {},
@@ -4040,6 +4099,9 @@ class ICloudPySessionMock(base.ICloudPySession):
             # IMG_3148.JPG another device
             or "https://cvws.icloud-content.com/B/Ab_8kUAhnGzSxnl9yWvh8JKBpOvWAVLSGMHt-PAQ9_krqqfXATNX57d5"
             in url
+            # IMG_5513.HEIC Shared Library
+            or "https://cvws.icloud-content.com/B/AY4eS1ezj9pmMHzfVzwC2CLmBwZOAXKLBx985QzfCKCGyN0wbGs6SuTf"
+            in url
         ):
             return ResponseMock(
                 {},
@@ -4069,6 +4131,9 @@ class ICloudPySessionMock(base.ICloudPySession):
             # IMG_3148.JPG another device
             or "https://cvws.icloud-content.com/B/AQNND5zpteAXnnBP2BmDd0ropjY0AV2Zh7WygJu74eNWVuuMT4lM8qme"
             in url
+            # IMG_5513.HEIC Shared Library
+            or "https://cvws.icloud-content.com/B/Aa_QVPVEM9bvm5Owy3GRFNyqbKuXAbgec55EhUFp9db5znXM3Xz-nq1X"
+            in url
         ):
             return ResponseMock(
                 {},
diff --git a/tests/data/photos_data.py b/tests/data/photos_data.py
index 87540157..94a6215a 100644
--- a/tests/data/photos_data.py
+++ b/tests/data/photos_data.py
@@ -2361,6 +2361,352 @@ DATA = {
                 "syncToken": "AQAAAAAAArKjf//////////fSxWSKv5JfZ34edrt875d",
             },
         },
+        # Shared library albums
+        {
+            "data": {
+                "query": {"recordType": "CPLAlbumByPositionLive"},
+                "zoneID": {
+                    "zoneName": "SharedSync-9DD9B767-9F30-4D6F-B658-F17DBA16D107",
+                    "ownerRecordName": "_fvhhqlzef1uvsgxnrw119mylkpjut1a0",
+                    "zoneType": "REGULAR_CUSTOM_ZONE",
+                },
+            },
+            "response": {
+                # No albums
+                "records": [],
+                "syncToken": "HwoECLiXBRgAIhUIs5Xxntqrrr9UEOLj2/q+geCjpAEoAA==",
+            },
+        },
+        # Contents of shared library all photos
+        {
+            "data": {
+                "query": {
+                    "filterBy": [
+                        {
+                            "fieldName": "startRank",
+                            "fieldValue": {"type": "INT64", "value": 0},
+                            "comparator": "EQUALS",
+                        },
+                        {
+                            "fieldName": "direction",
+                            "fieldValue": {"type": "STRING", "value": "ASCENDING"},
+                            "comparator": "EQUALS",
+                        },
+                    ],
+                    "recordType": "CPLAssetAndMasterByAddedDate",
+                },
+                "resultsLimit": 200,
+                "desiredKeys": [
+                    "resJPEGFullWidth",
+                    "resJPEGFullHeight",
+                    "resJPEGFullFileType",
+                    "resJPEGFullFingerprint",
+                    "resJPEGFullRes",
+                    "resJPEGLargeWidth",
+                    "resJPEGLargeHeight",
+                    "resJPEGLargeFileType",
+                    "resJPEGLargeFingerprint",
+                    "resJPEGLargeRes",
+                    "resJPEGMedWidth",
+                    "resJPEGMedHeight",
+                    "resJPEGMedFileType",
+                    "resJPEGMedFingerprint",
+                    "resJPEGMedRes",
+                    "resJPEGThumbWidth",
+                    "resJPEGThumbHeight",
+                    "resJPEGThumbFileType",
+                    "resJPEGThumbFingerprint",
+                    "resJPEGThumbRes",
+                    "resVidFullWidth",
+                    "resVidFullHeight",
+                    "resVidFullFileType",
+                    "resVidFullFingerprint",
+                    "resVidFullRes",
+                    "resVidMedWidth",
+                    "resVidMedHeight",
+                    "resVidMedFileType",
+                    "resVidMedFingerprint",
+                    "resVidMedRes",
+                    "resVidSmallWidth",
+                    "resVidSmallHeight",
+                    "resVidSmallFileType",
+                    "resVidSmallFingerprint",
+                    "resVidSmallRes",
+                    "resSidecarWidth",
+                    "resSidecarHeight",
+                    "resSidecarFileType",
+                    "resSidecarFingerprint",
+                    "resSidecarRes",
+                    "itemType",
+                    "dataClassType",
+                    "filenameEnc",
+                    "originalOrientation",
+                    "resOriginalWidth",
+                    "resOriginalHeight",
+                    "resOriginalFileType",
+                    "resOriginalFingerprint",
+                    "resOriginalRes",
+                    "resOriginalAltWidth",
+                    "resOriginalAltHeight",
+                    "resOriginalAltFileType",
+                    "resOriginalAltFingerprint",
+                    "resOriginalAltRes",
+                    "resOriginalVidComplWidth",
+                    "resOriginalVidComplHeight",
+                    "resOriginalVidComplFileType",
+                    "resOriginalVidComplFingerprint",
+                    "resOriginalVidComplRes",
+                    "isDeleted",
+                    "isExpunged",
+                    "dateExpunged",
+                    "remappedRef",
+                    "recordName",
+                    "recordType",
+                    "recordChangeTag",
+                    "masterRef",
+                    "adjustmentRenderType",
+                    "assetDate",
+                    "addedDate",
+                    "isFavorite",
+                    "isHidden",
+                    "orientation",
+                    "duration",
+                    "assetSubtype",
+                    "assetSubtypeV2",
+                    "assetHDRType",
+                    "burstFlags",
+                    "burstFlagsExt",
+                    "burstId",
+                    "captionEnc",
+                    "locationEnc",
+                    "locationV2Enc",
+                    "locationLatitude",
+                    "locationLongitude",
+                    "adjustmentType",
+                    "timeZoneOffset",
+                    "vidComplDurValue",
+                    "vidComplDurScale",
+                    "vidComplDispValue",
+                    "vidComplDispScale",
+                    "vidComplVisibilityState",
+                    "customRenderedValue",
+                    "containerId",
+                    "itemId",
+                    "position",
+                    "isKeyAsset",
+                ],
+                "zoneID": {
+                    "zoneName": "SharedSync-9DD9B767-9F30-4D6F-B658-F17DBA16D107",
+                    "ownerRecordName": "_0b5c3c201b3a7f1daac8ff7e7fbc0c35",
+                    "zoneType": "REGULAR_CUSTOM_ZONE",
+                },
+            },
+            "response": {
+                "records": [
+                    # IMG_5513.HEIC
+                    {
+                        "recordName": "AY4eS1ezj9pmMHzfVzwC2CLmBwZO",
+                        "recordType": "CPLMaster",
+                        "fields": {
+                            "itemType": {"value": "public.heic", "type": "STRING"},
+                            "resJPEGThumbFingerprint": {
+                                "value": "DmK0xzSiAUSFrAsYYAvby7QHrMDe",
+                                "type": "STRING",
+                            },
+                            "filenameEnc": {
+                                "value": "SU1HXzU1MTMuSEVJQw==",
+                                "type": "ENCRYPTED_BYTES",
+                            },
+                            "resJPEGMedRes": {
+                                "value": {
+                                    "fileChecksum": "EeGlt2PppPTgd0Q7mp8GenIugSh7",
+                                    "size": 1074354,
+                                    "wrappingKey": "amaCdL9Z+QxfzgD4+aYATg==",
+                                    "referenceChecksum": "AQYmx+DRYXnMs0tkDZ3rorp4IB99",
+                                    # pylint: disable=C0321
+                                    "downloadURL": "https://cvws.icloud-content.com/B/AQDN6auXvelQyb_btBqkNNjA97E2AZ_h3_ZBuSDV7J1SfMKpllmP-FGN/${f}?o=AkpvVanzvRePQKJ40Dm4YSvdVd9PwJFyrTyTz2oz9JPo&v=1&x=3&a=CAogVj_YvgW5M2-UxrKh2Fvp9mXaucTfmoEtSZ68oOF-ufUSbRDcod2wuDEY3P64srgxIgEAUgTA97E2WgSP-FGNaiZJrOc38Lxkny5PD-mG8_GkvEdMW-iCBLF63Zqz7ylzJVygR3BCr3ImsXnj-RgqNBe6eE3QOGEIQYnSIw1cO9KLmioI3AD_m-fQ-RL4Pfo&e=1698765356&fl=&r=1c7a694a-6450-4be0-85be-b8da170ce390-1&k=S2x_JT65p4HaFzealKoCTg&ckc=com.apple.photos.cloud&ckz=SharedSync-9DD9B767-9F30-4D6F-B658-F17DBA16D107&y=1&p=104&s=aw1qXk9b-vv16ld1xJ9OJ2lHSHk",  # noqa: E501
+                                },
+                                "type": "ASSETID",
+                            },
+                            "originalOrientation": {"value": 6, "type": "INT64"},
+                            "resJPEGMedHeight": {"value": 2048, "type": "INT64"},
+                            "resOriginalRes": {
+                                "value": {
+                                    "fileChecksum": "YN1v8eGiHYYZ/aKUkMuGtSf0P1BN",
+                                    "size": 2194253,
+                                    "wrappingKey": "Y40xDPUr6DmxfeoSqxaQ7A==",
+                                    "referenceChecksum": "AXKVYPcDa+9Mjvnap0ZS+p2Z24V3",
+                                    # pylint: disable=C0321
+                                    "downloadURL": "https://cvws.icloud-content.com/B/AY4eS1ezj9pmMHzfVzwC2CLmBwZOAXKLBx985QzfCKCGyN0wbGs6SuTf/${f}?o=AsBiWhNj_AjH-3vh0riCjYfhRtBfd-mUS6PQhxj4zYTH&v=1&x=3&a=CAogEeo3WZxkrjW9vg53X0JbMLiadhXSjhuENTh402ga9sISbRDdod2wuDEY3f64srgxIgEAUgTmBwZNWgQ6SuTfaiZ-CDCZGCdwB5-2MeuQ6hLpY6mmSYhtwgliGihERuDLhb89zqlO4HImWLHBIgl_7lBgvs7t6Ur5Vu_UAGoIQvIv1UEPwakPbNcGSCVnR5E&e=1698765356&fl=&r=1c7a694a-6450-4be0-85be-b8da170ce390-1&k=TF9BsVkUpEEdK0QpxQupmA&ckc=com.apple.photos.cloud&ckz=SharedSync-9DD9B767-9F30-4D6F-B658-F17DBA16D107&y=1&p=104&s=3nnkdl_tJa2baG_ccWB6PwXdlUM",  # noqa: E501
+                                },
+                                "type": "ASSETID",
+                            },
+                            "resJPEGMedFileType": {
+                                "value": "public.jpeg",
+                                "type": "STRING",
+                            },
+                            "resJPEGThumbHeight": {"value": 480, "type": "INT64"},
+                            "resJPEGThumbWidth": {"value": 360, "type": "INT64"},
+                            "resOriginalWidth": {"value": 4032, "type": "INT64"},
+                            "resJPEGThumbFileType": {
+                                "value": "public.jpeg",
+                                "type": "STRING",
+                            },
+                            "dataClassType": {"value": 1, "type": "INT64"},
+                            "resOriginalFingerprint": {
+                                "value": "YN1v8eGiHYYZ/aKUkMuGtSf0P1BN",
+                                "type": "STRING",
+                            },
+                            "resJPEGMedWidth": {"value": 1536, "type": "INT64"},
+                            "resJPEGThumbRes": {
+                                "value": {
+                                    "fileChecksum": "DmK0xzSiAUSFrAsYYAvby7QHrMDe",
+                                    "size": 340532,
+                                    "wrappingKey": "r7EeA3tyPsWdcECp6X9dHA==",
+                                    "referenceChecksum": "AR5TiM9Qko4rHwmoDH1BgNRVZpF4",
+                                    # pylint: disable=C0321
+                                    "downloadURL": "https://cvws.icloud-content.com/B/Aa_QVPVEM9bvm5Owy3GRFNyqbKuXAbgec55EhUFp9db5znXM3Xz-nq1X/${f}?o=AucoksyzdsJiFVmSYa_ajMHD_2grSFU6qbVTFdggYHgq&v=1&x=3&a=CAogsS3em6hEfTnsZDnYIlTaWjcN28xkDpe_JZFfm-ELLooSbRDdod2wuDEY3f64srgxIgEAUgSqbKuXWgT-nq1XaiZcxyPbeVdgxw86mBeD2193kkQ33hkJJh_JN1ApKm98JOLBPtcV4nImJH-ycgL-oJavf11qz9IfC5_g6vlq5shkb4Ohcl6xwdHgbBIcFYE&e=1698765356&fl=&r=1c7a694a-6450-4be0-85be-b8da170ce390-1&k=n8u2-NoAEqPijfVUBMbN6Q&ckc=com.apple.photos.cloud&ckz=SharedSync-9DD9B767-9F30-4D6F-B658-F17DBA16D107&y=1&p=104&s=m_19S8GYm58_yhhskaWeQNM6Nug",  # noqa: E501
+                                },
+                                "type": "ASSETID",
+                            },
+                            "resOriginalFileType": {
+                                "value": "public.heic",
+                                "type": "STRING",
+                            },
+                            "resOriginalHeight": {"value": 3024, "type": "INT64"},
+                            "resJPEGMedFingerprint": {
+                                "value": "EeGlt2PppPTgd0Q7mp8GenIugSh7",
+                                "type": "STRING",
+                            },
+                        },
+                        "pluginFields": {},
+                        "recordChangeTag": "1u4i",
+                        "created": {
+                            "timestamp": 1698625916645,
+                            "userRecordName": "_1",
+                            "deviceID": "89219292E3E8E16382FB69349FCA9ECA559613CD2FF3C3C8C8F7B420F1C347EG",
+                        },
+                        "modified": {
+                            "timestamp": 1698625916645,
+                            "userRecordName": "_1",
+                            "deviceID": "89219292E3E8E16382FB69349FCA9ECA559613CD2FF3C3C8C8F7B420F1C347EG",
+                        },
+                        "deleted": False,
+                        "zoneID": {
+                            "zoneName": "SharedSync-9DD9B767-9F30-4D6F-B658-F17DBA16D107",
+                            "ownerRecordName": "_0b5c3c201b3a7f1daac8ff7e7fbc0c35",
+                            "zoneType": "REGULAR_CUSTOM_ZONE",
+                        },
+                    },
+                    # Reference to IMG_5513.HEIC
+                    {
+                        "recordName": "92CBA085-7458-4B71-83E7-5696AA6C0437",
+                        "recordType": "CPLAsset",
+                        "fields": {
+                            "addedDate": {"value": 1698601166279, "type": "TIMESTAMP"},
+                            "resJPEGThumbFingerprint": {
+                                "value": "AfDQhuBu4hXYZG1MKMZww5hr0XFt",
+                                "type": "STRING",
+                            },
+                            "resJPEGFullFingerprint": {
+                                "value": "AVUZeNmSEsGH47OEyy0AojnozAK8",
+                                "type": "STRING",
+                            },
+                            "adjustmentType": {
+                                "value": "com.apple.photo",
+                                "type": "STRING",
+                            },
+                            "assetSubtypeV2": {"value": 0, "type": "INT64"},
+                            "resJPEGFullHeight": {"value": 1060, "type": "INT64"},
+                            "assetHDRType": {"value": 0, "type": "INT64"},
+                            "timeZoneOffset": {"value": -25200, "type": "INT64"},
+                            "masterRef": {
+                                "value": {
+                                    "recordName": "AY4eS1ezj9pmMHzfVzwC2CLmBwZO",
+                                    "action": "DELETE_SELF",
+                                    "zoneID": {
+                                        "zoneName": "SharedSync-9DD9B767-9F30-4D6F-B658-F17DBA16D107",
+                                        "ownerRecordName": "_0b5c3c201b3a7f1daac8ff7e7fbc0c35",
+                                        "zoneType": "REGULAR_CUSTOM_ZONE",
+                                    },
+                                },
+                                "type": "REFERENCE",
+                            },
+                            "locationEnc": {
+                                "value": "YnBsaXN0MDDYAQIDBAUGBwgJCgsMCg0OD1Zjb3Vyc2VVc3BlZWRTYWx0U2xvbld2ZXJ0QWNjU2",
+                                "type": "ENCRYPTED_BYTES",
+                            },
+                            "adjustmentRenderType": {"value": 2816, "type": "INT64"},
+                            "vidComplDispScale": {"value": 0, "type": "INT64"},
+                            "duration": {"value": 0, "type": "INT64"},
+                            "burstFlags": {"value": 0, "type": "INT64"},
+                            "assetSubtype": {"value": 0, "type": "INT64"},
+                            "resJPEGThumbWidth": {"value": 360, "type": "INT64"},
+                            "vidComplVisibilityState": {"value": 0, "type": "INT64"},
+                            "resJPEGFullWidth": {"value": 795, "type": "INT64"},
+                            "resJPEGFullRes": {
+                                "value": {
+                                    "fileChecksum": "AVUZeNmSEsGH47OEyy0AojnozAK8",
+                                    "size": 340860,
+                                    "wrappingKey": "KaI99WFTkCwtFMUF+tYrSQ==",
+                                    "referenceChecksum": "AYsP/+uTEhISEYYHCK+nasKEFIie",
+                                    # pylint: disable=C0321
+                                    "downloadURL": "https://cvws.icloud-content.com/B/AVUZeNmSEsGH47OEyy0AojnozAK8AYsP_-uTEhISEYYHCK-nasKEFIie/${f}?o=AkNOIvUnrfPDQ5THreKWKcvRplOk2ggvZF6KsaY7fd-A&v=1&x=3&a=CAogmm1yJGk7Y1lG58D_XQVryIZ6o11DEv3NIaN6iIPSKtMSbRCBpevFuDEYgYLHx7gxIgEAUgTozAK8WgSEFIieaiaHabDKDv5faXcmtytIOOSUbK4AWjU1YWdR46bN82z7JbfcangVG3ImUXOt030VnBVdnXbi-kYl85xzaurON3eMgxiRpnHZOHf0eem7cHY&e=1698809626&fl=&r=b39216b7-2243-48c2-92cb-b598e98e0d60-1&k=KaI99WFTkCwtFMUF-tYrSQ&ckc=com.apple.photos.cloud&ckz=SharedSync-9DD9B767-9F30-4D6F-B658-F17DBA16D106&y=1&p=104&s=7ATOXQ_ALygO3TEGReKS2XM-L_Q",  # noqa: E501
+                                },
+                                "type": "ASSETID",
+                            },
+                            "assetDate": {"value": 1698601165886, "type": "TIMESTAMP"},
+                            "resJPEGFullFileType": {
+                                "value": "public.heic",
+                                "type": "STRING",
+                            },
+                            "orientation": {"value": 1, "type": "INT64"},
+                            "isHidden": {"value": 0, "type": "INT64"},
+                            "resJPEGThumbHeight": {"value": 480, "type": "INT64"},
+                            "vidComplDurScale": {"value": 0, "type": "INT64"},
+                            "resJPEGThumbFileType": {
+                                "value": "public.jpeg",
+                                "type": "STRING",
+                            },
+                            "vidComplDurValue": {"value": 0, "type": "INT64"},
+                            "resJPEGThumbRes": {
+                                "value": {
+                                    "fileChecksum": "AfDQhuBu4hXYZG1MKMZww5hr0XFt",
+                                    "size": 130373,
+                                    "wrappingKey": "OhxadeoR+QL9gyG5vjchQQ==",
+                                    "referenceChecksum": "AVIWcEe38h3rZ6jdx0xradApmsm5",
+                                    # pylint: disable=C0321
+                                    "downloadURL": "https://cvws.icloud-content.com/B/AfDQhuBu4hXYZG1MKMZww5hr0XFtAVIWcEe38h3rZ6jdx0xradApmsm5/${f}?o=Av--_tK-30ay30YC4r-3ioGEUXi0lv_jiR4L9IqMnIN1&v=1&x=3&a=CAogyaYaP5z_6cAm4ENgJsdpmA7mEWWm0bNV2NE_uHkHO5QSbRCCpevFuDEYgoLHx7gxIgEAUgRr0XFtWgQpmsm5aibYv1Ug7RFU3CVYUoQwEQushhNxX3GrnGlEPU0JXThR4gnzI-UuA3ImA-Q5OOhz9lBTE-KMkuTizKv8KxrchdoxZyDpoPXumnzP-Lz8Z9Q&e=1698809626&fl=&r=b39216b7-2243-48c2-92cb-b598e98e0d60-1&k=OhxadeoR-QL9gyG5vjchQQ&ckc=com.apple.photos.cloud&ckz=SharedSync-9DD9B767-9F30-4D6F-B658-F17DBA16D106&y=1&p=104&s=XERpve2UemTKkPC2qUKPjEAq_fI",  # noqa: E501
+                                },
+                                "type": "ASSETID",
+                            },
+                            "customRenderedValue": {"value": 10, "type": "INT64"},
+                            "isFavorite": {"value": 1, "type": "INT64"},
+                            "vidComplDispValue": {"value": 0, "type": "INT64"},
+                        },
+                        "pluginFields": {},
+                        "recordChangeTag": "1u4j",
+                        "created": {
+                            "timestamp": 1698625916670,
+                            "userRecordName": "_1",
+                            "deviceID": "89219292E3E8E16382FB69349FCA9ECA559613CD2FF3C3C8C8F7B420F1C347EF",
+                        },
+                        "modified": {
+                            "timestamp": 1698625916670,
+                            "userRecordName": "_1",
+                            "deviceID": "89219292E3E8E16382FB69349FCA9ECA559613CD2FF3C3C8C8F7B420F1C347EF",
+                        },
+                        "deleted": False,
+                        "zoneID": {
+                            "zoneName": "SharedSync-9DD9B767-9F30-4D6F-B658-F17DBA16D107",
+                            "ownerRecordName": "_0b5c3c201b3a7f1daac8ff7e7fbc0c35",
+                            "zoneType": "REGULAR_CUSTOM_ZONE",
+                        },
+                    },
+                ],
+                "syncToken": "HwoECLiXBRgAIhUIs5Xxntqrrr9UEOLj2/q+geCjpAEoAA==",
+            },
+        },
     ],
     "query/batch?remapEnums=True&getCurrentSyncToken=True": [
         {
diff --git a/tests/data/test_config.yaml b/tests/data/test_config.yaml
index 89112e28..3187d45d 100644
--- a/tests/data/test_config.yaml
+++ b/tests/data/test_config.yaml
@@ -34,6 +34,11 @@ drive:
     - "*.psd"
     - .git/
   filters:
+    # List of libraries to download. If omitted (default), photos from all libraries (own and shared) are downloaded. If included, photos only
+    # from the listed libraries are downloaded.
+    # libraries:
+    #   - PrimarySync # Name of the own library
+
     # File filters to be included in syncing iCloud drive content
     folders:
       - dir1/dir2/dir3
@@ -56,6 +61,9 @@ photos:
   all_albums: false # Optional, default false. If true preserve album structure. If same photo is in multpile albums creates duplicates on filesystem
   # folder_format: "%Y/%m" # optional, if set put photos in subfolders according to format. Format cheatsheet - https://strftime.org
   filters:
+    # libraries: # Optional, specify list of libraries to download photos from
+    #   - PrimarySync # Library of the user
+    #   - SharedSync-abcd # Library of another user
     # if all_albums is false - albums list is used as filter-in, if all_albums is true - albums list is used as filter-out
     # if albums list is empty and all_albums is false download all photos to "all" folder. if empty and all_albums is true download all folders
     albums:
diff --git a/tests/test_config_parser.py b/tests/test_config_parser.py
index b0e5bf83..1b1d8a7b 100644
--- a/tests/test_config_parser.py
+++ b/tests/test_config_parser.py
@@ -444,3 +444,22 @@ class TestConfigParser(unittest.TestCase):
         config = read_config(config_path=tests.CONFIG_PATH)
         config["photos"]["folder_format"] = "%Y/%m"
         self.assertEqual(config_parser.get_photos_folder_format(config=config), "%Y/%m")
+
+    def test_get_photos_filters_libraries_empty(self):
+        """Photos > library is missing in config."""
+        config = read_config(config_path=tests.CONFIG_PATH)
+        self.assertEqual(
+            config_parser.get_photos_filters(config=config)["libraries"], None
+        )
+
+    def test_get_photos_filters_libraries_specified(self):
+        """Photos > library is specified as shared."""
+        config = read_config(config_path=tests.CONFIG_PATH)
+        config["photos"]["filters"]["libraries"] = [
+            "PrimarySync",
+            "SharedSync-9DD9B767-9F30-4D6F-B658-F17DBA16D107",
+        ]
+        self.assertListEqual(
+            config_parser.get_photos_filters(config=config)["libraries"],
+            config["photos"]["filters"]["libraries"],
+        )
diff --git a/tests/test_sync_photos.py b/tests/test_sync_photos.py
index 329b2033..b55804e8 100644
--- a/tests/test_sync_photos.py
+++ b/tests/test_sync_photos.py
@@ -1,6 +1,7 @@
 """Tests for sync_photos.py file."""
 __author__ = "Mandar Patil (mandarons@pm.me)"
 
+import glob
 import os
 import shutil
 import unittest
@@ -47,6 +48,7 @@ class TestSyncPhotos(unittest.TestCase):
         mock_service = self.service
         config = self.config.copy()
         config["photos"]["destination"] = self.destination_path
+        config["photos"]["filters"]["libraries"] = ["PrimarySync"]
         mock_read_config.return_value = config
         # Sync original photos
         self.assertIsNone(
@@ -639,3 +641,29 @@ class TestSyncPhotos(unittest.TestCase):
         self.assertTrue(os.path.isdir(album_1_path))
         self.assertTrue(len(os.listdir(album_0_path)) == 1)
         self.assertTrue(len(os.listdir(album_1_path)) == 0)
+
+    @patch(target="keyring.get_password", return_value=data.VALID_PASSWORD)
+    @patch(
+        target="src.config_parser.get_username", return_value=data.AUTHENTICATED_USER
+    )
+    @patch("icloudpy.ICloudPyService")
+    @patch("src.read_config")
+    def test_photo_download_with_shared_libraries(
+        self, mock_read_config, mock_service, mock_get_username, mock_get_password
+    ):
+        """Test for downloading photos from shared libraries."""
+        mock_service = self.service
+        config = self.config.copy()
+        config["photos"]["destination"] = self.destination_path
+        del config["photos"]["filters"]["albums"]
+        mock_read_config.return_value = config
+        # Sync original photos
+        self.assertIsNone(
+            sync_photos.sync_photos(config=config, photos=mock_service.photos)
+        )
+        all_path = os.path.join(self.destination_path, "all")
+        self.assertTrue(os.path.isdir(all_path))
+        # Check for PrimarySync photo
+        self.assertTrue(len(glob.glob(os.path.join(all_path, "IMG_3148*.JPG"))) > 0)
+        # Check for shared photo
+        self.assertTrue(len(glob.glob(os.path.join(all_path, "IMG_5513*.HEIC"))) > 0)