Search code examples
bashsortingdatescriptingrename

Rename and sort pictures and videos from Google Takeout


I'm trying to automatically rename and sort pictures and videos from a Google Photos export (Google Takeout) using a bash script. Structure of the Google Photos directory after download:

.
├── Google Photos
│   ├── Accident Mercedes
│   │   ├── IMG_9660.JPG
│   │   ├── IMG_9660.JPG.json
│   │   └── metadata.json
│   ├── Ardennen vrienden Ine
│   │   ├── 0906b711-2fc6-4b29-8daf-1203aad2f2ca.jpg
│   │   ├── 0906b711-2fc6-4b29-8daf-1203aad2f2ca.jpg.json
│   │   ├── 41ADBC31-C2FB-4489-A316-81464EA04F18.mp4
│   │   ├── 41ADBC31-C2FB-4489-A316-81464EA04F18.mp4.json
│   │   ├── IMG_0414.JPG
│   │   ├── IMG_0414.JPG.json
│   │   └── metadata.json
│   ├── Audi Q3 Ine
│   │   ├── IMG_5602.JPG
│   │   ├── IMG_5602.JPG.json
│   │   ├── IMG_5614.MOV
│   │   ├── IMG_5614.MOV.json
│   │   └── metadata.json
...

End result I would like to achieve, where the filename is formatted like %d%m%Y_%H%M%S%2N.lower_case_extension and the directories are grouped per year and split over two main video and photo directories:

.
├── Photo
│   ├── 2022
│   │   ├── Accident Mercedes
│   │   │   ├── 17052022_15563312.jpg
│   │   │   ├── 17052022_15563817.jpg
│   │   │   ├── 17052022_15564918.jpg
│   │   ├── Ardennen vrienden Ine
│   │   │   ├── 20072022_21241609.jpg
│   │   │   ├── 20072022_21292212.jpg
│   │   │   ├── 20072022_21310924.jpg
├── Video
│   ├── 2022
│   │   ├── Accident Mercedes
│   │   │   ├── 17052022_15563316.mp4
│   │   │   ├── 17052022_15563818.mov
│   │   ├── Ardennen vrienden Ine
│   │   │   ├── 20072022_21241645.mp4
...

I've tried creating a bash script for the renaming part, however it's not working, since it seems to use last edit date and not creation date, despite -r option. Unable to figure out how to lowercase the extension.

#!/bin/bash

cd "$1"

for d in *; do
  cd "$d"
  for f in *.jpg *.JPG *.MOV *.mov *.mp4 *.MP4 *.heic; do
    echo "./$d/$f"
    new_name=$(date -r "$f" +"%d%m%Y_%H%M%S%2N")
    ext="${f##*.}"
    mv "$f" "${new_name}.${ext}"
  done

  rm -rf "*.desktop.ini"
  rm -rf "*.json"
done

Remarks:

  1. I need to use milliseconds to avoid files being removed if taken at exact same second.
  2. I would like to remove all *.json and *.ini files.

I've tried creating another bash script for the sorting part, however it's only moving files and not directories containing those files.

#!/bin/bash

cd "$1"
mkdir Video
cd Video
mkdir -p {2000..2024}

c=2000
while [ $c -lt 2024 ]; do
   find "$2" -iname "*$c_*.jpg" -print0 | xargs -0 -I {} mv {} ./$c
   let c=c+1
done

cd "$1"
mkdir Photos
cd Photos
mkdir -p {2000..2024}

c=2000
while [ $c -lt 2024 ]; do
   find "$2" -iname "*$c_*.mp4" -print0 | xargs -0 -I {} mv {} ./$c
   let c=c+1
done

Thanks in advance for help!


Solution

  • A subshell can be used for the cd, you could try something like this.

    #!/usr/bin/env bash
    
    shopt -s nullglob
    GooglePhotos=(Google\ Photos/*/)
    shopt -u nullglob
    
    for d in "${GooglePhotos[@]}" ; do
      cwd=
      ext=
      file_type=
      mod_year=
      old_file_name=
      new_file_name=
      declare -a files
      (
        cd "$d" || exit
        shopt -s nullglob
        files=(*)
        shopt -u nullglob
    
        for f in "${!files[@]}"; do
          if [[ "${files[$f]}" == *.@(ini|json) ]]; then
            # rm -rf -- "${files[$f]}"
            break
          else
            ext=${files[$f]##*.}
            ext=${ext,,}
            old_file_name="${files[$f]}"
            cwd="$(basename -- "$PWD")"
            mod_year="$(date -r "${files[$f]}" '+%Y')"
            file_type="$(file --mime-type -b -- "${files[$f]}")"
            new_file_name="$(date -r "${files[$f]}" '+%d%m%Y_%H%M%S%2N').${ext}"
    
            if [[ "$file_type" == video/* ]]; then
              mkdir -pv "../Google/Video/$mod_year/$cwd" || exit
              cp -v -- "$old_file_name" "../Google/Video/$mod_year/$cwd/$new_file_name" || exit
            elif [[ "$file_type" == image/* ]]; then
              mkdir -pv "../Google/Photo/$mod_year/$cwd/" || exit
              cp -v -- "$old_file_name" "../Google/Photo/$mod_year/$cwd/$new_file_name" || exit
            fi
          fi
          unset -v 'files[f]'
        done
        files=("${files[@]}")
      )
    done
    

    • A directory named Google/ is created below/one level of your Google Photo directory.
    • Nothing is deleted or moved, files ending in .ini and .json. are ignored,
    • Copies the files with the new directory/file names accordingly.
    • Uncomment the rm and change cp to mv if satisfied with the output/outcome/result.
    • Change the destination directory to your own hearts content.
    • Use stat as mentioned in the comments for the creation date, although it might not be available on some os/file system.
    • See How Can I check If a directory is empty (Consider dotglob also)
    • Why check the error of the cd command (The cd in a subshell included)
    • See Check/compare variable value multiple times for the *.@(ini|json) glob/pattern.