Skip to content

adsamcik/PhotoCopy

Repository files navigation

PhotoCopy

Allows batch moving/copying photos to a new location organized by time.

How to use

The following command will copy all files in /input/path to /output/path/with/variables where each file will retain its relative directory but will be placed in addition year/month/extension directory inside the relative structure. Duplicate files will be appended with -number (eg. -1). And all files will be moved instead of copied.

./PhotoCopy -i "/input/path" -o "/output/path/with/variables/{directory}/{year}/{month}/{extension}/{name}" --duplicate-format "-{number}" --mode "Move"

Options

-i, --input Required. Path to a source directory, which will be scanned for files.

-o, --output Required. Destination path for the operation. Determines the final path files have. Supported variables (case-sensitive): {year}, {month}, {day}, {dayOfYear}, {directory}, {name}, {nameNoExtension}, {extension}, {camera}, {album}

  • {year} - year extracted from file creation date or exif
  • {month} - month extracted from file creation date or exif
  • {day} - day extracted from file creation date or exif
  • {dayOfYear} - day of year (~1-365) from file creation date or exif
  • {directory} - relative directory structure from input path
  • {name} - file name with extension
  • {nameNoExtension} - file name without extension
  • {extension} - file extension
  • {camera} - camera make/model from EXIF metadata (e.g., "Canon EOS R5")
  • {album} - album name from EXIF/XMP/IPTC metadata

-d, --dry Only prints what will happen without actually doing it. It is recommended to combine it with log level verbose.

-m, --mode (Default: copy) Operation mode. Available modes: copy, move

-l, --logLevel (Default: important) Determines how much information is printed on the screen. Options: verbose, important, errorsOnly

--no-skip-duplicate Disables duplicate skipping.

__--duplicate-format (Default: _{number}) Format used for differentiating files with the same name. Use {number} for number placeholder.

--skip-existing Skips file if it already exists in the output.

--max-depth Maximum directory recursion depth when scanning for files. 0 or omit for unlimited (default), 1 = root directory only, 2 = root + one level of subdirectories, etc.


Location-Based Organization

PhotoCopy supports organizing photos by geographic location using GPS metadata from your photos. Location variables include:

  • {district} - Neighborhood or district name
  • {city} - City name
  • {county} - County/region name
  • {state} - State/province name
  • {country} - Country name or code

Customizing Unknown Location Handling

When a photo lacks GPS metadata or the location can't be determined, PhotoCopy provides flexible options for handling these cases.

Global Fallback Setting

Set a default fallback in appsettings.yaml:

photoCopy:
  unknownLocationFallback: "Unknown"  # Default folder for photos without location

Inline Fallbacks

Override the global fallback for specific variables using the pipe (|) syntax:

# Use "NoLocation" instead of the global fallback
./PhotoCopy -i "/photos" -o "/sorted/{year}/{city|NoLocation}/{name}"

Variable Chains

Try multiple location levels in order, falling back through the chain:

# Try city first, then country if city is unavailable
./PhotoCopy -i "/photos" -o "/sorted/{year}/{city|country}/{name}"

# Three-level chain: try district, then city, then country
./PhotoCopy -i "/photos" -o "/sorted/{year}/{district|city|country}/{name}"

# Chain ending with a literal fallback
./PhotoCopy -i "/photos" -o "/sorted/{year}/{city|country|Unknown}/{name}"

Empty Fallbacks

Omit a folder entirely when the variable is unavailable:

# If city is unknown, the folder level is simply omitted
./PhotoCopy -i "/photos" -o "/sorted/{year}/{city|}/{month}/{name}"
# Photos with city: /sorted/2024/Prague/01/photo.jpg
# Photos without:   /sorted/2024/01/photo.jpg

Conditional Variables (Threshold-Based)

Use location values only when you have enough photos to justify a separate folder:

# Only create city folders if there are at least 10 photos from that city
./PhotoCopy -i "/photos" -o "/sorted/{year}/{city?min=10|country}/{name}"

How it works: PhotoCopy performs a two-pass operation:

  1. First pass: Scans all files and counts photos per location
  2. Second pass: Applies thresholds and organizes files

Example results with {city?min=10|country}:

Source: 15 photos from Prague, 3 from Brno, 8 from Vienna

Output:
  - Prague photos → /sorted/2024/Prague/photo.jpg     (15 ≥ 10, uses city)
  - Brno photos   → /sorted/2024/CZ/photo.jpg         (3 < 10, falls back to country)
  - Vienna photos → /sorted/2024/AT/photo.jpg         (8 < 10, falls back to country)

Advanced threshold examples:

# Maximum threshold: use city only if ≤100 photos (avoid huge folders)
./PhotoCopy -i "/photos" -o "/sorted/{city?max=100|country}/{name}"

# Range: use city if between 10-100 photos
./PhotoCopy -i "/photos" -o "/sorted/{city?min=10,max=100|country}/{name}"

# Chained conditions: city if ≥10, else country if ≥5, else literal
./PhotoCopy -i "/photos" -o "/sorted/{city?min=10|country?min=5|Misc}/{name}"

Live Photo Support

PhotoCopy automatically handles iPhone Live Photos, which consist of:

  • A .heic or .jpg image with full EXIF metadata (including GPS)
  • A companion .mov video that often lacks GPS metadata

Automatic Metadata Inheritance

When enableLivePhotoInheritance is enabled (default: true), companion .mov videos automatically inherit GPS/location data from their paired photo file with the same base name.

Example:

Source files:
  IMG_1234.heic  (has GPS: Tokyo, Japan)
  IMG_1234.mov   (no GPS)

Result:
  IMG_1234.mov inherits GPS from IMG_1234.heic
  Both files will be organized to the same location folder

Configuration

photoCopy:
  # Enable/disable Live Photo metadata inheritance (default: true)
  enableLivePhotoInheritance: true
  
  # Related files mode - ensures paired files stay together
  relatedFileMode: Strict  # Strict, Loose, or None

How It Works

  1. Scanning Phase: PhotoCopy scans all files in the source directory
  2. Live Photo Pass: Before organization, it identifies .heic/.jpg + .mov pairs by matching base names
  3. Metadata Transfer: Videos without GPS inherit location data from their paired photo
  4. Organization: Both files are organized to the same destination folder

Supported Formats

Photo Extension Video Extension Notes
.heic .mov Standard iPhone Live Photo format
.heif .mov Alternative HEIF format
.jpg / .jpeg .mov Some devices use JPEG for Live Photos

Related File Handling

For Live Photos to stay together, ensure relatedFileMode is set to Strict or Loose:

./PhotoCopy -i "/photos" -o "/sorted/{year}/{city}/{name}" --related-file-mode Strict

This ensures both the .heic and .mov files are copied together to the same destination.

About

No description, website, or topics provided.

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published