Repository: mandarons/icloud-drive-docker
Pull request #220 (2024-06-20 19:24:57)

Handle original_alt version extensions
Pulled in changes from PR #213


Patch:
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..b4f70059
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,5 @@
+{
+  "python.testing.pytestArgs": ["tests", "--no-cov"],
+  "python.testing.unittestEnabled": false,
+  "python.testing.pytestEnabled": true
+}
diff --git a/src/sync_photos.py b/src/sync_photos.py
index 1b442be3..647d5536 100644
--- a/src/sync_photos.py
+++ b/src/sync_photos.py
@@ -11,6 +11,55 @@ from icloudpy import exceptions
 
 from src import LOGGER, config_parser
 
+original_alt_filetype_to_extension = {
+    "public.png": "png",
+    "public.jpeg": "jpeg",
+    "public.heic": "heic",
+    "public.image": "HEIC",
+    "com.sony.arw-raw-image": "arw",
+    "org.webmproject.webp": "webp",
+    "com.compuserve.gif": "gif",
+    "com.adobe.raw-image": "dng",
+    "public.tiff": "tiff",
+    "public.jpeg-2000": "jp2",
+    "com.truevision.tga-image": "tga",
+    "com.sgi.sgi-image": "sgi",
+    "com.adobe.photoshop-image": "psd",
+    "public.pbm": "pbm",
+    "public.heif": "heif",
+    "com.microsoft.bmp": "bmp",
+    "com.fuji.raw-image": "raf",
+    "com.canon.cr2-raw-image": "cr2",
+    "com.panasonic.rw2-raw-image": "rw2",
+    "com.nikon.nrw-raw-image": "nrw",
+    "com.pentax.raw-image": "pef",
+    "com.nikon.raw-image": "nef",
+    "com.olympus.raw-image": "orf",
+    "com.adobe.pdf": "pdf",
+    "com.canon.cr3-raw-image": "cr3",
+    "com.olympus.or-raw-image": "orf",
+    "public.mpo-image": "mpo",
+    "com.dji.mimo.pano.jpeg": "jpg",
+    "public.avif": "avif",
+    "com.canon.crw-raw-image": "crw",
+}
+
+
+def get_name_and_extension(photo, file_size):
+    """Extract filename and extension."""
+    filename = photo.filename
+    name, extension = filename.rsplit(".", 1) if "." in filename else [filename, ""]
+    if file_size == "original_alt" and file_size in photo.versions:
+        filetype = photo.versions[file_size]["type"]
+        if filetype in original_alt_filetype_to_extension:
+            extension = original_alt_filetype_to_extension[filetype]
+        else:
+            LOGGER.warning(
+                f"Unknown filetype {filetype} for "
+                f"original_alt version of {filename}"
+            )
+    return name, extension
+
 
 def photo_wanted(photo, extensions):
     """Check if photo is wanted based on extension."""
@@ -25,7 +74,7 @@ def photo_wanted(photo, extensions):
 def generate_file_name(photo, file_size, destination_path, folder_format):
     """Generate full path to file."""
     filename = photo.filename
-    name, extension = filename.rsplit(".", 1) if "." in filename else [filename, ""]
+    name, extension = get_name_and_extension(photo, file_size)
     file_path = os.path.join(destination_path, filename)
     file_size_path = os.path.join(
         destination_path,


Test patch:
diff --git a/tests/data/photos_data.py b/tests/data/photos_data.py
index 94a6215a..0573f0f7 100644
--- a/tests/data/photos_data.py
+++ b/tests/data/photos_data.py
@@ -399,22 +399,48 @@ DATA = {
                                 },
                                 "type": "ASSETID",
                             },
+                            "resOriginalWidth": {"value": 3100, "type": "INT64"},
+                            "resOriginalFingerprint": {
+                                "value": "YN1v8eGiHYYZ/aKUkMuGtSf0P1BN",
+                                "type": "STRING",
+                            },
+                            "resOriginalFileType": {
+                                "value": "public.jpeg",
+                                "type": "STRING",
+                            },
+                            "resOriginalHeight": {"value": 2398, "type": "INT64"},
+                            "resOriginalAltRes": {
+                                "value": {
+                                    "fileChecksum": "YN1v8eGiHYYZ/aKUkMuGtSf0P1BN",
+                                    "size": 2194253,
+                                    "wrappingKey": "Y40xDPUr6DmxfeoSqxaQ7A==",
+                                    "referenceChecksum": "AXKVYPcDa+9Mjvnap0ZS+p2Z24V3",
+                                    # pylint: disable=C0321
+                                    "downloadURL": "https://cvws.icloud-content.com/B/YN1v8eGiHYYZ_aKUkMuGtSf0P1BNAXKVYPcDa-9Mjvnap0ZS-p2Z24V3/${f}?o=Ame-Q1e_1nWqIn7YG7VfVZk-XAs8bVdcHo-owaNRmfPn&v=1&x=3&a=CAogwS503Q9EkCdnzvD-kLG0VNwrlEmARONCS-hADMtqg1QSbRCckq2nty8YnO-IqbcvIgEAUgT0P1BNWgSZ24V3aiYLdjzdjGLXPtKfjwtH_PG0ralgbDDBOIftNXxyRxdhzz8OuZztNnImb65YPlo1qUOy4i7tW1pcyAZcjqS8kYfxPQD6SKIAKNk3dUid7mE&e=1629757781&fl=&r=4d5c62f6-c81b-4e60-a785-4139aad087a7-1&k=Y40xDPUr6DmxfeoSqxaQ7A&ckc=com.apple.photos.cloud&ckz=PrimarySync&y=1&p=104&s=X91oiOo0Avp6TR4d27MGupd_cqY",  # noqa: E501
+                                },
+                                "type": "ASSETID",
+                            },
+                            "resOriginalAltWidth": {"value": 3100, "type": "INT64"},
+                            "resOriginalAltFingerprint": {
+                                "value": "YN1v8eGiHYYZ/aKUkMuGtSf0P1BN",
+                                "type": "STRING",
+                            },
+                            "resOriginalAltFileType": {
+                                "value": "public.jpeg",
+                                "type": "STRING",
+                            },
+                            "resOriginalAltHeight": {"value": 2398, "type": "INT64"},
                             "resJPEGMedFileType": {
                                 "value": "public.jpeg",
                                 "type": "STRING",
                             },
                             "resJPEGThumbHeight": {"value": 365, "type": "INT64"},
                             "resJPEGThumbWidth": {"value": 472, "type": "INT64"},
-                            "resOriginalWidth": {"value": 3100, "type": "INT64"},
                             "resJPEGThumbFileType": {
                                 "value": "public.jpeg",
                                 "type": "STRING",
                             },
                             "dataClassType": {"value": 1, "type": "INT64"},
-                            "resOriginalFingerprint": {
-                                "value": "YN1v8eGiHYYZ/aKUkMuGtSf0P1BN",
-                                "type": "STRING",
-                            },
                             "resJPEGMedWidth": {"value": 2016, "type": "INT64"},
                             "resJPEGThumbRes": {
                                 "value": {
@@ -427,11 +453,6 @@ DATA = {
                                 },
                                 "type": "ASSETID",
                             },
-                            "resOriginalFileType": {
-                                "value": "public.jpeg",
-                                "type": "STRING",
-                            },
-                            "resOriginalHeight": {"value": 2398, "type": "INT64"},
                             "resJPEGMedFingerprint": {
                                 "value": "EeGlt2PppPTgd0Q7mp8GenIugSh7",
                                 "type": "STRING",
@@ -2541,6 +2562,17 @@ DATA = {
                                 },
                                 "type": "ASSETID",
                             },
+                            "resOriginalAltRes": {
+                                "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",
@@ -2548,6 +2580,7 @@ DATA = {
                             "resJPEGThumbHeight": {"value": 480, "type": "INT64"},
                             "resJPEGThumbWidth": {"value": 360, "type": "INT64"},
                             "resOriginalWidth": {"value": 4032, "type": "INT64"},
+                            "resOriginalAltWidth": {"value": 4032, "type": "INT64"},
                             "resJPEGThumbFileType": {
                                 "value": "public.jpeg",
                                 "type": "STRING",
@@ -2557,6 +2590,10 @@ DATA = {
                                 "value": "YN1v8eGiHYYZ/aKUkMuGtSf0P1BN",
                                 "type": "STRING",
                             },
+                            "resOriginalAltFingerprint": {
+                                "value": "YN1v8eGiHYYZ/aKUkMuGtSf0P1BN",
+                                "type": "STRING",
+                            },
                             "resJPEGMedWidth": {"value": 1536, "type": "INT64"},
                             "resJPEGThumbRes": {
                                 "value": {
@@ -2573,7 +2610,12 @@ DATA = {
                                 "value": "public.heic",
                                 "type": "STRING",
                             },
+                            "resOriginalAltFileType": {
+                                "value": "public.heic",
+                                "type": "STRING",
+                            },
                             "resOriginalHeight": {"value": 3024, "type": "INT64"},
+                            "resOriginalAltHeight": {"value": 3024, "type": "INT64"},
                             "resJPEGMedFingerprint": {
                                 "value": "EeGlt2PppPTgd0Q7mp8GenIugSh7",
                                 "type": "STRING",
diff --git a/tests/test_sync_photos.py b/tests/test_sync_photos.py
index c8ee5442..8c10582c 100644
--- a/tests/test_sync_photos.py
+++ b/tests/test_sync_photos.py
@@ -5,6 +5,7 @@ import glob
 import os
 import shutil
 import unittest
+from io import StringIO
 from unittest.mock import patch
 
 import icloudpy
@@ -702,3 +703,45 @@ class TestSyncPhotos(unittest.TestCase):
         self.assertTrue(os.path.isdir(album_0_path))
         self.assertTrue(os.path.isdir(album_1_path))
         self.assertTrue(os.path.isdir(album_2_path))
+
+    @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_get_name_and_extension(
+        self, mock_read_config, mock_service, mock_get_username, mock_get_password
+    ):
+        """Test for successful original_alt photo size download."""
+        mock_service = self.service
+        config = self.config.copy()
+        config["photos"]["destination"] = self.destination_path
+        config["photos"]["filters"]["file_sizes"] = ["original_alt"]
+        mock_read_config.return_value = config
+        # Sync original photos
+        self.assertIsNone(
+            sync_photos.sync_photos(config=config, photos=mock_service.photos)
+        )
+        album_0_path = os.path.join(
+            self.destination_path, config["photos"]["filters"]["albums"][0]
+        )
+        album_1_path = os.path.join(
+            self.destination_path, config["photos"]["filters"]["albums"][1]
+        )
+        self.assertTrue(os.path.isdir(album_0_path))
+        self.assertTrue(os.path.isdir(album_1_path))
+
+    @patch(target="sys.stderr", new_callable=StringIO)
+    def test_get_name_and_extension_warning(self, mock_stdout):
+        """Test warning generation for invalid original_alt extension."""
+
+        class MockPhoto:
+            filename = "mock_filename.xed"
+            versions = {"original_alt": {"type": "invalid"}}
+
+        name, extension = sync_photos.get_name_and_extension(
+            photo=MockPhoto(), file_size="original_alt"
+        )
+        self.assertEqual(name, "mock_filename")
+        self.assertEqual(extension, "xed")