Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- fix: `--only-print-filenames` downloads live photo video files during deduplication [#1220](https://github.com/icloud-photos-downloader/icloud_photos_downloader/issues/1220)

## 1.32.1 (2025-08-30)

- fix: KeyError when downloading photos with --size adjusted --size alternative options [#926](https://github.com/icloud-photos-downloader/icloud_photos_downloader/issues/926)
Expand Down
19 changes: 17 additions & 2 deletions src/icloudpd/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -785,8 +785,23 @@ def download_builder(

lp_file_exists = os.path.isfile(lp_download_path)

if only_print_filenames and not lp_file_exists:
print(lp_download_path)
if only_print_filenames:
if not lp_file_exists:
print(lp_download_path)
# Handle deduplication case for only_print_filenames
if (
lp_file_exists
and file_match_policy == FileMatchPolicy.NAME_SIZE_DEDUP_WITH_SUFFIX
):
lp_file_size = os.stat(lp_download_path).st_size
lp_photo_size = version.size
if lp_file_size != lp_photo_size:
lp_download_path = (f"-{lp_photo_size}.").join(
lp_download_path.rsplit(".", 1)
)
logger.debug("%s deduplicated", truncate_middle(lp_download_path, 96))
# Print the deduplicated filename but don't download
print(lp_download_path)
else:
if lp_file_exists:
if file_match_policy == FileMatchPolicy.NAME_SIZE_DEDUP_WITH_SUFFIX:
Expand Down
96 changes: 96 additions & 0 deletions tests/test_issue_1220_only_print_filenames_dedup_bug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""Test for issue #1220: --only-print-filenames downloads live photo video files during deduplication

https://github.com/icloud-photos-downloader/icloud_photos_downloader/issues/1220

Bug Description:
- When using --only-print-filenames, no files should be downloaded
- However, during live photo deduplication, the MOV file gets downloaded
- This happens because the deduplication logic changes the filename and resets file_exists to False
- The download then proceeds despite only_print_filenames being True
"""

import inspect
import os
from typing import List, Tuple
from unittest import TestCase

import pytest

from tests.helpers import (
path_from_project_root,
run_icloudpd_test,
)


class Issue1220OnlyPrintFilenamesDeduplicationBugTest(TestCase):
"""Test case to reproduce the bug where --only-print-filenames downloads files during deduplication"""

@pytest.fixture(autouse=True)
def inject_fixtures(self) -> None:
self.root_path = path_from_project_root(__file__)
self.fixtures_path = os.path.join(self.root_path, "fixtures")

def test_only_print_filenames_should_not_download_during_deduplication(self) -> None:
"""
Test that --only-print-filenames works correctly with deduplication.

This test reproduces issue #1220 and verifies the fix.
"""
base_dir = os.path.join(self.fixtures_path, inspect.stack()[0][3])

# Create files that will trigger deduplication scenarios
# We create files with different sizes than what the mock will return
files_to_create: List[Tuple[str, str, int]] = [
(os.path.join("2018", "07", "31"), "IMG_7409.MOV", 100), # Small size to trigger deduplication
]

# With --only-print-filenames, NO files should be downloaded
files_to_download: List[Tuple[str, str]] = []

data_dir, result = run_icloudpd_test(
self.assertEqual,
self.root_path,
base_dir,
"download_live_photos.yml", # Use a VCR cassette that has live photos
files_to_create,
files_to_download,
[
"--username",
"jdoe@gmail.com",
"--password",
"password1",
"--recent",
"3",
"--only-print-filenames", # This should prevent ALL downloads
"--file-match-policy",
"name-size-dedup-with-suffix", # Enable deduplication
"--no-progress-bar",
"--threads-num",
"1",
],
)

# The command should succeed
assert result.exit_code == 0

# Filenames should be printed to stdout
filenames = result.output.splitlines()
self.assertGreater(len(filenames), 0, "Some filenames should be printed")

# Check that the directory contains only the files we created (no downloads)
actual_files = []
for root, _dirs, files in os.walk(data_dir):
for file in files:
rel_path = os.path.relpath(os.path.join(root, file), data_dir)
actual_files.append(rel_path)

# Only the file we created should exist (no downloads should have happened)
expected_files = [os.path.join("2018", "07", "31", "IMG_7409.MOV")]

# After the fix, this should pass
self.assertEqual(
sorted(actual_files),
sorted(expected_files),
"Only pre-created files should exist. No files should be downloaded "
"when --only-print-filenames is used, even during deduplication.",
)
Loading