Skip to content

Instantly share code, notes, and snippets.

@tnymlr
Last active December 21, 2019 22:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tnymlr/44a01f21c3f9af8657592e1f74954732 to your computer and use it in GitHub Desktop.
Save tnymlr/44a01f21c3f9af8657592e1f74954732 to your computer and use it in GitHub Desktop.
Download photos from an iPhone and convert HEIC to JPGs and MOVs to VP9 MKVs. Details: https://til.tmlr.xyz/notes/linux-ios-photo-sync/
#!/usr/bin/env bash
do_log() {
local scope=$1
local msg=$2
echo "[${scope}]: ${msg}"
}
do_error() {
do_log "$1" "$2" >&2
}
log() {
do_log "${FUNCNAME[1]}" "$1"
}
error() {
do_error "${FUNCNAME[1]}" "$1"
exit 3
}
#!/bin/sh
set -e
source "${HOME}/src/dotfiles/lib/log.sh"
CACHE_DIR="${HOME}/.cache/phsync"
ORIGINALS_DIR="${CACHE_DIR}/originals"
MOUNT_DIR="${CACHE_DIR}/phone"
SOURCE_DIR="${MOUNT_DIR}/DCIM"
PICTURES_DIR="${HOME}/Pictures"
PHOTOS_DIR="${PICTURES_DIR}/Photos"
TARGET_DIR="${PHOTOS_DIR}/Camera"
RSYNC_DONE="${PHOTOS_DIR}/rsync.done"
umount_phone() {
log "Unmounting phone from: $MOUNT_DIR"
if [ "$(mount | grep ifuse | wc -l)" -lt 1 ]; then
error "Nothing to unmount"
fi
while true; do
if fusermount -u "$MOUNT_DIR"; then
log "Phone is unmounted"
break
else
log "Failed to unmount phone, retrying in 1 sec"
sleep 1s
fi
done
}
trap "umount_phone; exit" ERR EXIT
validate_setup() {
log "Validating the environment..."
if which heif-convert; then
log "Found 'heif-convert' installed"
else
error "'heif-convert' is required to convert photos, install libheif"
fi
if which HandBrakeCLI; then
log "Found 'HandBrakeCLI' installed"
else
error "'HandBrakeCLI is require to convert videos"
fi
}
ensure_dirs() {
log "Preparing directories..."
mkdir -p "${MOUNT_DIR}"
mkdir -p "${TARGET_DIR}"
mkdir -p "${ORIGINALS_DIR}"
touch "${RSYNC_DONE}"
}
mount_phone() {
log "Mounting phone to: $MOUNT_DIR"
if ifuse "$MOUNT_DIR"; then
log "Phone is mounted at: $MOUNT_DIR"
else
error "Failed to mount phone at: $MOUNT_DIR"
fi
}
sync_photos() {
local target="$TARGET_DIR"
if [ $target == "" ]; then
error "Target can't be empty"
fi
log "Synchronizing photos [target=$target]"
if find "$SOURCE_DIR" -mindepth 1 -type d -name "*APPLE" -exec rsync \
-azvhP \
--size-only \
--ignore-times \
--no-perms \
--no-owner \
--no-group \
--no-times \
--exclude-from="$RSYNC_DONE" \
{}/ \
"$target" \;; then
log "Photo sync completed"
else
umount_phone
error "Failed to sync photos"
fi
}
record_changes() {
log "Marking received photos as 'synchronized'"
find "${TARGET_DIR}" -type f -exec basename {} \; | xargs -I {} /bin/sh -c \
'grep -qxF "{}" '"$RSYNC_DONE"' || echo "{}" >> '"$RSYNC_DONE"
}
process_photos_from_to() {
local from="$1"
local to="$2"
log "Converting photos from: $from to: $to"
if [ "$from" == "" ]; then
error "'from' argument is not specified"
fi
if [ "$to" == "" ]; then
error "'to' argument is not specified"
fi
find "${TARGET_DIR}" -type f -name "*.$from" -exec basename -s .$from {} \; | \
xargs -I {} /bin/sh -c \
'heif-convert '"${TARGET_DIR}"'/{}.'"$from"' '"${TARGET_DIR}"'/{}.'"$to"' && mv '"${TARGET_DIR}"'/{}.'"$from"' '"${ORIGINALS_DIR}/"
}
process_photos() {
log "Converting photos..."
process_photos_from_to HEIC JPG
process_photos_from_to HEIF JPG
log "Done converting photos"
}
process_videos() {
log "Converting videos..."
find "${TARGET_DIR}" -type f -name "*.MOV" -exec basename -s .MOV {} \; | \
xargs -I {} /bin/sh -c \
'HandBrakeCLI -i '"${TARGET_DIR}"'/{}.MOV -o '"${TARGET_DIR}"'/{}.MKV --preset="VP9 MKV 2160p60" && mv '"${TARGET_DIR}"'/{}.MOV '"${ORIGINALS_DIR}/"
log "Done converting videos"
}
sync() {
validate_setup
ensure_dirs
mount_phone
sync_photos
record_changes
process_photos
process_videos
umount_phone
}
sync
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment