Skip to content

Commit dbc26be

Browse files
teh-hippoCopilot
andcommitted
feat: split --write-metadata into xmp/exif variants
Add --write-metadata-xmp and --write-metadata-exif for independent control of XMP sidecar generation and EXIF tag writing. --write-metadata remains as shorthand for both. All three flags accept category lists: all, rating, keywords, title, datetime, dates, orientation, location. Omitting value defaults to 'all'. New categories: - datetime: DateTimeOriginal + CreateDate - dates: datetime + ModifyDate (superset) Deprecations (still functional, with warnings): - --xmp-sidecar -> --write-metadata-xmp - --set-exif-datetime -> --write-metadata-exif datetime Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent b4add71 commit dbc26be

File tree

4 files changed

+83
-23
lines changed

4 files changed

+83
-23
lines changed

src/icloudpd/base.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -291,8 +291,8 @@ def run_with_configs(global_config: GlobalConfig, user_configs: Sequence[UserCon
291291
# Create shared logger
292292
logger = create_logger(global_config)
293293

294-
# Check exiftool availability if any user has --write-metadata
295-
if any(uc.write_metadata for uc in user_configs):
294+
# Check exiftool availability if any user has --write-metadata-exif
295+
if any(uc.write_metadata_exif for uc in user_configs):
296296
from icloudpd.metadata_writer import ExiftoolNotFoundError, check_exiftool
297297
try:
298298
ver = check_exiftool()
@@ -473,12 +473,12 @@ def password_provider(_username: str) -> str | None:
473473
user_config.force_size,
474474
global_config.only_print_filenames,
475475
user_config.set_exif_datetime,
476-
user_config.write_metadata,
476+
user_config.write_metadata_exif,
477477
user_config.skip_live_photos,
478478
user_config.live_photo_size,
479479
user_config.dry_run,
480480
user_config.file_match_policy,
481-
user_config.xmp_sidecar,
481+
user_config.write_metadata_xmp,
482482
lp_filename_generator,
483483
filename_builder,
484484
user_config.align_raw,
@@ -890,12 +890,12 @@ def download_builder(
890890
force_size: bool,
891891
only_print_filenames: bool,
892892
set_exif_datetime: bool,
893-
write_metadata_config: frozenset[str],
893+
write_metadata_exif: frozenset[str],
894894
skip_live_photos: bool,
895895
live_photo_size: LivePhotoVersionSize,
896896
dry_run: bool,
897897
file_match_policy: FileMatchPolicy,
898-
xmp_sidecar: bool,
898+
write_metadata_xmp: frozenset[str],
899899
lp_filename_generator: Callable[[str], str],
900900
filename_builder: Callable[[PhotoAsset], str],
901901
raw_policy: RawTreatmentPolicy,
@@ -1144,10 +1144,10 @@ def download_builder(
11441144
)
11451145
logger.info("Downloaded %s", truncated_path)
11461146

1147-
if xmp_sidecar:
1148-
generate_xmp_file(logger, download_path, photo._asset_record, dry_run, dir_cache)
1147+
if write_metadata_xmp:
1148+
generate_xmp_file(logger, download_path, photo._asset_record, dry_run, dir_cache)
11491149

1150-
if write_metadata_config:
1150+
if write_metadata_exif:
11511151
# Skip in-file metadata for videos — XMP sidecar is the canonical source.
11521152
# QuickTime Keys metadata is redundant when the sidecar exists.
11531153
if os.path.splitext(download_path.lower())[1] not in (".mov", ".mp4", ".m4v", ".avi"):

src/icloudpd/cli.py

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def map_align_raw_to_enum(align_raw_str: str) -> RawTreatmentPolicy:
3434
return mapping[align_raw_str]
3535

3636

37-
_VALID_WRITE_METADATA = {"all", "rating", "keywords", "title", "dates", "datetime", "orientation", "location"}
37+
_VALID_WRITE_METADATA = {"all", "rating", "keywords", "title", "dates", "orientation", "location", "datetime"}
3838

3939

4040
def _parse_write_metadata(value: str | None) -> frozenset[str]:
@@ -161,11 +161,40 @@ def add_options_for_user(parser: argparse.ArgumentParser) -> argparse.ArgumentPa
161161
cloned.add_argument(
162162
"--write-metadata",
163163
help=(
164-
"Write iCloud metadata into file EXIF/XMP using exiftool. "
164+
"Write iCloud metadata as both XMP sidecars and EXIF tags. "
165+
"Shorthand for --write-metadata-xmp and --write-metadata-exif. "
165166
"Comma-separated list of field categories: "
166-
"all, rating, keywords, title, dates, orientation. "
167-
"Requires exiftool to be installed. Default: disabled."
167+
"all, rating, keywords, title, datetime, dates, orientation, location. "
168+
"Requires exiftool to be installed. "
169+
"Default when specified without value: all."
168170
),
171+
nargs="?",
172+
const="all",
173+
default=None,
174+
)
175+
cloned.add_argument(
176+
"--write-metadata-xmp",
177+
help=(
178+
"Write iCloud metadata as XMP sidecar files. "
179+
"Comma-separated list of field categories: "
180+
"all, rating, keywords, title, datetime, dates, orientation, location. "
181+
"Default when specified without value: all."
182+
),
183+
nargs="?",
184+
const="all",
185+
default=None,
186+
)
187+
cloned.add_argument(
188+
"--write-metadata-exif",
189+
help=(
190+
"Write iCloud metadata into file EXIF/XMP tags using exiftool. "
191+
"Comma-separated list of field categories: "
192+
"all, rating, keywords, title, datetime, dates, orientation, location. "
193+
"Requires exiftool to be installed. "
194+
"Default when specified without value: all."
195+
),
196+
nargs="?",
197+
const="all",
169198
default=None,
170199
)
171200

@@ -456,6 +485,37 @@ def format_help() -> str:
456485

457486

458487
def map_to_config(user_ns: argparse.Namespace) -> UserConfig:
488+
# Resolve write-metadata flags
489+
write_metadata_base = _parse_write_metadata(user_ns.write_metadata)
490+
write_metadata_xmp = _parse_write_metadata(user_ns.write_metadata_xmp)
491+
write_metadata_exif = _parse_write_metadata(user_ns.write_metadata_exif)
492+
493+
# --write-metadata sets both if specific variants not given
494+
if write_metadata_base:
495+
if not write_metadata_xmp:
496+
write_metadata_xmp = write_metadata_base
497+
if not write_metadata_exif:
498+
write_metadata_exif = write_metadata_base
499+
500+
# --xmp-sidecar is deprecated alias for --write-metadata-xmp all
501+
if user_ns.xmp_sidecar:
502+
import logging
503+
504+
logging.getLogger("icloudpd").warning(
505+
"--xmp-sidecar is deprecated, use --write-metadata-xmp instead"
506+
)
507+
if not write_metadata_xmp:
508+
write_metadata_xmp = frozenset({"all"})
509+
510+
# --set-exif-datetime adds datetime to exif set
511+
if user_ns.set_exif_datetime:
512+
import logging
513+
514+
logging.getLogger("icloudpd").warning(
515+
"--set-exif-datetime is deprecated, use --write-metadata-exif datetime instead"
516+
)
517+
write_metadata_exif = write_metadata_exif | frozenset({"datetime"})
518+
459519
return UserConfig(
460520
username=user_ns.username,
461521
password=user_ns.password,
@@ -474,12 +534,12 @@ def map_to_config(user_ns: argparse.Namespace) -> UserConfig:
474534
list_libraries=user_ns.list_libraries,
475535
skip_videos=user_ns.skip_videos,
476536
skip_live_photos=user_ns.skip_live_photos,
477-
xmp_sidecar=user_ns.xmp_sidecar,
478537
force_size=user_ns.force_size,
479538
auto_delete=user_ns.auto_delete,
480539
folder_structure=user_ns.folder_structure,
481540
set_exif_datetime=user_ns.set_exif_datetime,
482-
write_metadata=_parse_write_metadata(user_ns.write_metadata),
541+
write_metadata_xmp=write_metadata_xmp,
542+
write_metadata_exif=write_metadata_exif,
483543
smtp_username=user_ns.smtp_username,
484544
smtp_password=user_ns.smtp_password,
485545
smtp_host=user_ns.smtp_host,

src/icloudpd/config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ class _DefaultConfig:
2727
list_libraries: bool
2828
skip_videos: bool
2929
skip_live_photos: bool
30-
xmp_sidecar: bool
3130
force_size: bool
3231
auto_delete: bool
3332
folder_structure: str
3433
set_exif_datetime: bool
35-
write_metadata: frozenset[str]
34+
write_metadata_xmp: frozenset[str]
35+
write_metadata_exif: frozenset[str]
3636
smtp_username: str | None
3737
smtp_password: str | None
3838
smtp_host: str

tests/test_cli.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -211,12 +211,12 @@ def test_cli_parser(self) -> None:
211211
list_libraries=False,
212212
skip_videos=False,
213213
skip_live_photos=False,
214-
xmp_sidecar=False,
215214
force_size=False,
216215
auto_delete=False,
217216
folder_structure="{:%Y/%m/%d}",
218217
set_exif_datetime=False,
219-
write_metadata=frozenset(),
218+
write_metadata_xmp=frozenset(),
219+
write_metadata_exif=frozenset(),
220220
smtp_username=None,
221221
smtp_password=None,
222222
smtp_host="smtp.gmail.com",
@@ -252,12 +252,12 @@ def test_cli_parser(self) -> None:
252252
list_libraries=False,
253253
skip_videos=False,
254254
skip_live_photos=False,
255-
xmp_sidecar=False,
256255
force_size=False,
257256
auto_delete=False,
258257
folder_structure="{:%Y/%m/%d}",
259258
set_exif_datetime=False,
260-
write_metadata=frozenset(),
259+
write_metadata_xmp=frozenset(),
260+
write_metadata_exif=frozenset(),
261261
smtp_username=None,
262262
smtp_password=None,
263263
smtp_host="smtp.gmail.com",
@@ -329,12 +329,12 @@ def test_cli_parser(self) -> None:
329329
list_libraries=False,
330330
skip_videos=False,
331331
skip_live_photos=False,
332-
xmp_sidecar=False,
333332
force_size=False,
334333
auto_delete=False,
335334
folder_structure="{:%Y/%m/%d}",
336335
set_exif_datetime=False,
337-
write_metadata=frozenset(),
336+
write_metadata_xmp=frozenset(),
337+
write_metadata_exif=frozenset(),
338338
smtp_username=None,
339339
smtp_password=None,
340340
smtp_host="smtp.gmail.com",

0 commit comments

Comments
 (0)