Last active
December 29, 2024 17:20
-
-
Save ryochack/d5fcfb51a628f01e9ed679dad760d107 to your computer and use it in GitHub Desktop.
Migration Scripts from Google Photo to Synology Photos
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# Overwrite EXIF AllDates(DateTimeOriginal, DateTime, DateTimeDigitized) with photoTakenTime value of sidecar json | |
# The simplest way is to execute the following command, but this method cannot handle the "xxx(1).jpg" / "xxx.jpg(1).json" pair. | |
# $ exiftool -overwrite_original -d "%s" -tagsFromFile "%d/%F.json" "-AllDates<PhotoTakenTimeTimestamp" -r ${DIR} | |
function usage() { | |
cat << EOS | |
usage: $0 [JPG or DIR] | |
This script overwrites the DateTimeOriginal value in the EXIF of the JPG with the PhotoTakenTime value in the sidecar json file. | |
EOS | |
} | |
function overwrite_exif_with_json() { | |
local jpg="$1" | |
local json | |
if [[ -f "${jpg}.json" ]]; then | |
json="${jpg}.json" | |
elif [[ "${jpg}" =~ ^(.+)(\([0-9]+\))(\..+)$ ]]; then | |
# For some reason, Google Photo names the xxx(1).jpg sidecar file as xxx.jpg(1).json. | |
# Therefore, find xxx.jpg(1).json from xxx.jpg(1).jpg. | |
body="${BASH_REMATCH[1]}" # xxx | |
bracket="${BASH_REMATCH[2]}" # (1) | |
ext="${BASH_REMATCH[3]}" # .jpg | |
json="${body}${ext}${bracket}.json" # xxx.jpg(1).json | |
if [[ ! -f "${json}" ]]; then | |
# Missing JSON | |
return 0 | |
fi | |
else | |
# Missing JSON | |
return 0 | |
fi | |
exiftool -overwrite_original -d "%s" -tagsFromFile "${json}" "-AllDates<PhotoTakenTimeTimestamp" "${jpg}" -q | |
} | |
# ========== main ========== | |
ORG_IFS=${IFS} | |
if [ $# -eq 0 ]; then | |
usage | |
exit 1 | |
else | |
TARGET="$1" | |
fi | |
if [[ -f ${TARGET} ]]; then | |
if [[ "${TARGET##*.}" != "jpg" ]]; then | |
echo "ERROR: require JPG file" | |
usage | |
exit 1 | |
fi | |
validate_exif_and_json_time "${TARGET}" | |
elif [[ -d "${TARGET}" ]]; then | |
IFS=$'\n' | |
dir_list+=($(find ${TARGET} -mindepth 1 -maxdepth 1 -type d | sort)) | |
for subdir in "${dir_list[@]}" ; do | |
echo ${subdir} | |
IFS=$'\n' | |
jpg_list=($(find "${subdir}" -type f -iname "*.jpg")) | |
IFS=${ORG_IFS} | |
for jpg in "${jpg_list[@]}" ; do | |
overwrite_exif_with_json "${jpg}" | |
done | |
done | |
else | |
echo "${TARGET} is unknown type" | |
fi |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# Checking target | |
# * Year/Month/Day/Hour/Minutes of EXIF (DateTimeOriginal) | |
# * Year/Month/Day/Hour/Minutes of JSON (photoTakenTime) | |
# * (Year of dir name) | |
DEFAULT_TZ="Asia/Tokyo" | |
function usage() { | |
cat << EOS | |
usage: $0 [JPG or DIR] | |
This script checks the validity of the timestamps of photos downloaded from Google Takeout. | |
Specifically, it compares the DateTimeOriginal value in the EXIF of the JPG with the PhotoTakenTime value in the sidecar json file. | |
EOS | |
} | |
function validate_exif_and_json_time() { | |
jpg="$1" | |
# Read EXIF DateTimeOriginal | |
exif_err="" | |
exif_time=$(exiftool -T -d "%Y %m %d %H %M %S" -DateTimeOriginal "${jpg}") | |
status=$? | |
if [ ${status} -ne 0 ]; then | |
echo "${jpg} : Failed to read EXIF. It might be broken file." | |
return 1 | |
fi | |
if [[ "${exif_time}" == "-" ]]; then | |
exif_err="Missing EXIF" | |
else | |
read eyear emonth eday ehours emin esec <<< $(awk '{print $1, $2, $3, $4, $5, $6}' <<< ${exif_time}) | |
if [[ ! "${eyear}" =~ ^[0-9]{4}$ ]]; then | |
exif_err="Invalid EXIF(${eyear}-${emonth}-${eday}T${ehours}:${emin}:${esec})" | |
fi | |
fi | |
# Read json photoTakenTime.timestamp | |
json_err="" | |
if [[ -f "${jpg}.json" ]]; then | |
json="${jpg}.json" | |
elif [[ "${jpg}" =~ ^(.+)(\([0-9]+\))(\..+)$ ]]; then | |
# For some reason, Google Photo names the xxx(1).jpg sidecar file as xxx.jpg(1).json. | |
# Therefore, find xxx.jpg(1).json from xxx.jpg(1).jpg. | |
body="${BASH_REMATCH[1]}" # xxx | |
bracket="${BASH_REMATCH[2]}" # (1) | |
ext="${BASH_REMATCH[3]}" # .jpg | |
json="${body}${ext}${bracket}.json" # xxx.jpg(1).json | |
if [[ ! -f "${json}" ]]; then | |
json_err="Missing JSON" | |
fi | |
else | |
json_err="Missing JSON" | |
fi | |
if [[ -z "${json_err}" ]]; then | |
json_time=$(TZ="${DEFAULT_TZ}" jq -r '.photoTakenTime.timestamp | tonumber | strflocaltime("%Y %m %d %H %M %S")' "${json}") | |
read jyear jmonth jday jhours jmin jsec <<< $(awk '{print $1, $2, $3, $4, $5, $6}' <<< $json_time) | |
if [[ ! ${jyear} =~ ^[0-9]{4}$ ]]; then | |
json_err="Invalid JSON(${jyear}-${jmonth}-${jday}T${jhours}:${jmin}:${jsec})" | |
fi | |
fi | |
# Dump error messages if there are problems | |
if [[ -n "${exif_err}" && -n "${json_err}" ]]; then | |
echo "${jpg} : ${exif_err} and ${json_err}" | |
elif [[ -n "${exif_err}" ]]; then | |
echo "${jpg} : ${exif_err}" | |
elif [[ -n "${json_err}" ]]; then | |
echo "${jpg} : ${json_err}" | |
else | |
if [[ "${eyear}" != "${jyear}" || "${emonth}" != "${jmonth}" \ | |
|| "${eday}" != "${jday}" || "${ehours}" != "${jhours}" \ | |
|| "${emin}" != "${jmin}" || "${esec}" != "${jsec}" ]]; then | |
echo "${jpg} : Differ Time" | |
echo " EXIF: ${eyear}-${emonth}-${eday}T${ehours}:${emin}:${esec}" | |
echo " JSON: ${jyear}-${jmonth}-${jday}T${jhours}:${jmin}:${jsec}" | |
fi | |
fi | |
} | |
# ========== main ========== | |
ORG_IFS=${IFS} | |
if [ $# -eq 0 ]; then | |
usage | |
exit 1 | |
else | |
TARGET="$1" | |
fi | |
if [[ -f ${TARGET} ]]; then | |
if [[ "${TARGET##*.}" != "jpg" ]]; then | |
echo "ERROR: require JPG file" | |
usage | |
exit 1 | |
fi | |
validate_exif_and_json_time "${TARGET}" | |
elif [[ -d "${TARGET}" ]]; then | |
dir_years=($(find ${TARGET_DIR} -maxdepth 1 -type d -name "Photos from *" | sort | awk '{print $3}')) | |
for dyear in "${dir_years[@]}" ; do | |
subdir="Photos from ${dyear}" | |
IFS=$'\n' | |
jpg_list=($(find "${subdir}" -type f -iname "*.jpg")) | |
IFS=${ORG_IFS} | |
for jpg in "${jpg_list[@]}" ; do | |
validate_exif_and_json_time "${jpg}" | |
done | |
done | |
else | |
echo "${TARGET} is unknown type" | |
fi |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
set -eu | |
EXIFTOOL_VER="12.99" | |
INSTALL_DIR=/usr/local/share | |
BIN_DIR=/usr/local/bin | |
function usage() { | |
cat << EOS | |
usage: $0 | |
This script installs ExifTool to Synology DSM7 | |
options: | |
-u : uninstall exiftool | |
-h : show this help | |
EOS | |
} | |
function exiftool_install() { | |
wget https://exiftool.org/Image-ExifTool-${EXIFTOOL_VER}.tar.gz | |
tar xzf Image-ExifTool-${EXIFTOOL_VER}.tar.gz | |
rm Image-ExifTool-${EXIFTOOL_VER}.tar.gz | |
sudo -u root mv Image-ExifTool-${EXIFTOOL_VER} ${INSTALL_DIR}/ExifTool | |
sudo -u root ln -s ${INSTALL_DIR}/ExifTool/exiftool ${BIN_DIR}/exiftool | |
} | |
function exiftool_uninstall() { | |
sudo -u root rm -rf ${BIN_DIR}/exiftool ${INSTALL_DIR}/ExifTool | |
} | |
if [ "$#" -eq 0 ]; then | |
echo "Install exiftool..." | |
exiftool_install | |
echo "Installed exiftool" | |
else | |
case $1 in | |
-u) | |
echo "Uninstall exiftool..." | |
exiftool_uninstall | |
echo "Uninstalled exiftool" | |
;; | |
-h) | |
usage | |
;; | |
*) | |
usage | |
exit 1 | |
;; | |
esac | |
fi |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
set -eu | |
USERNAME=$(whoami) | |
function usage() { | |
cat << EOS | |
usage: $0 (OPTION) [extension...] | |
This script sorts photos and movies for Synology Photos. | |
Default target extensions are bellow. | |
jpg jpeg | |
arw nrf sr2 cr2 cr3 orf raf rw2 | |
avi mov m4a mp4 mpg mpeg | |
To add target other extensions, speciry them as arguments. | |
e.g. $0 xml toml | |
Options: | |
--shared : Set Synology shared photo directory to target directory. | |
Default is personal photo direcotry "/var/services/homes/${USERNAME}/Photos" | |
If this option is set, Use "/var/services/photo". | |
--dir DIR: Set target photo directory manually. | |
EOS | |
} | |
PERSONAL_PHOTO_DIR=/var/services/homes/${USERNAME}/Photos | |
SHARED_PHOTO_DIR=/var/services/photo | |
IMAGE_EXTENSIONS=("jpg" "jpeg") | |
RAW_EXTENSIONS=("arw" "nrf" "sr2" "cr2" "cr3" "orf" "raf" "rw2") | |
MOVIE_EXTENSIONS=("avi" "mov" "m4a" "mp4" "mpg" "mpeg") | |
EXTENSIONS_LOWERCASE=(${IMAGE_EXTENSIONS[@]} ${RAW_EXTENSIONS[@]} ${MOVIE_EXTENSIONS[@]}) | |
EXTENSIONS_UPPERCASE=(${IMAGE_EXTENSIONS[@]^^} ${RAW_EXTENSIONS[@]^^} ${MOVIE_EXTENSIONS[@]^^}) | |
DEFAULT_TARGET_EXTENSIONS=(${EXTENSIONS_LOWERCASE[@]} ${EXTENSIONS_UPPERCASE[@]}) | |
UNSORT_DIRNAME="UnsortablePhotos" | |
PHOTO_DIR=${PERSONAL_PHOTO_DIR} | |
TARGET_EXTENSIONS=(${DEFAULT_TARGET_EXTENSIONS[@]}) | |
# === Parse arguments === | |
while (( $# > 0 )); do | |
if [[ "$1" == "-h" || "$1" == "--help" ]]; then | |
usage | |
exit 0 | |
elif [[ "$1" == "--shared" ]]; then | |
PHOTO_DIR=${SHARED_PHOTO_DIR} | |
elif [[ "$1" == "--dir" ]]; then | |
shift | |
if [[ $# == 0 ]]; then | |
usage | |
exit 1 | |
fi | |
PHOTO_DIR="$1" | |
else | |
TARGET_EXTENSIONS+=("$1") | |
fi | |
shift | |
done | |
echo "target photo dir: ${PHOTO_DIR}" | |
echo "target extensions: [${TARGET_EXTENSIONS[@]}]" | |
# === Sort photos uploaded to Synology Photos into date directories(${PHOTO_DIR}/%Y/%m) === | |
exiftool -ExtractEmbedded -d "${PHOTO_DIR}/PhotoLibrary/%Y/%m" "-Directory<CreateDate" "-Directory<DateTimeOriginal" ${PHOTO_DIR} | |
# === Move unsorted files (they have no time info) === | |
function get_extensions_exist() { | |
local extensions=("$@") | |
found_extensions=() | |
for ext in "${extensions[@]}"; do | |
if ls ${PHOTO_DIR}/*."${ext}" &> /dev/null; then | |
# found | |
found_extensions+=("${ext}") | |
fi | |
done | |
printf "%s\n" "${found_extensions[@]}" | sort | uniq | |
} | |
extensions=($(get_extensions_exist ${TARGET_EXTENSIONS[@]})) | |
if [[ -n "${extensions[@]}" ]]; then | |
# Convert from (jpg mp4 ...) to "${PHOTO_DIR}/*.jpg ${PHOTO_DIR}/*.mp4 ..." | |
unsortable_files="${extensions[*]/#/${PHOTO_DIR}/*.}" | |
install -d "${PHOTO_DIR}/${UNSORT_DIRNAME}" | |
mv ${unsortable_files} "${PHOTO_DIR}/${UNSORT_DIRNAME}" | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment