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

0 stars

Watchers

1 watching

Forks

Packages

 
 
 

Contributors