Skip to main content
  • Android
  • Note

Android Photo Export Pitfall

Published 17 June 2024

adb pull can mess up file timestamps when exporting photos from Android if you forget -a.

adb pull -a /storage/emulated/0/DCIM ./DCIM

That flag is easy to miss, but it saves a lot of cleanup later.

Some of my files were already messed up, so I used a small Python script afterward to repair the timestamps. It was vibe-coded rather than properly written, but it worked well enough for this one-off job. The script checks the filename first, then falls back to EXIF metadata via exiftool.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import re
import time
import subprocess
from datetime import datetime

def run_exiftool(file_path):
    try:
        cmd = ['exiftool', '-DateTimeOriginal', '-s3', file_path]
        result = subprocess.run(cmd, capture_output=True, text=True)

        if result.returncode == 0 and result.stdout.strip():
            date_str = result.stdout.strip()
            try:
                return datetime.strptime(date_str, "%Y:%m:%d %H:%M:%S").timestamp()
            except ValueError:
                print(f"Could not parse DateTimeOriginal: {date_str}")

        cmd = ['exiftool', '-CreateDate', '-s3', file_path]
        result = subprocess.run(cmd, capture_output=True, text=True)

        if result.returncode == 0 and result.stdout.strip():
            date_str = result.stdout.strip()
            try:
                return datetime.strptime(date_str, "%Y:%m:%d %H:%M:%S").timestamp()
            except ValueError:
                print(f"Could not parse CreateDate: {date_str}")

        cmd = ['exiftool', '-FileModifyDate', '-s3', file_path]
        result = subprocess.run(cmd, capture_output=True, text=True)

        if result.returncode == 0 and result.stdout.strip():
            date_str = result.stdout.strip()
            try:
                if "+" in date_str:
                    date_str = date_str.split("+").strip()
                return datetime.strptime(date_str, "%Y:%m:%d %H:%M:%S").timestamp()
            except ValueError:
                print(f"Could not parse FileModifyDate: {date_str}")

    except Exception as e:
        print(f"Failed to read EXIF data: {file_path} - {str(e)}")

    return None

def extract_timestamp_from_filename(filename):
    match = re.search(r'IMG_(\d{8})_(\d{6})', filename)
    if match:
        date_str, time_str = match.groups()
        dt_str = f"{date_str[:4]}-{date_str[4:6]}-{date_str[6:]} {time_str[:2]}:{time_str[2:4]}:{time_str[4:]}"
        return datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S").timestamp()

    match = re.search(r'Screenshot_(\d{4}-\d{2}-\d{2})-(\d{2})-(\d{2})-(\d{2})', filename)
    if match:
        year_month_day, hour, minute, second = match.groups()
        dt_str = f"{year_month_day} {hour}:{minute}:{second}"
        return datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S").timestamp()

    match = re.search(r'VID_(\d{8})_(\d{6})', filename)
    if match:
        date_str, time_str = match.groups()
        dt_str = f"{date_str[:4]}-{date_str[4:6]}-{date_str[6:]} {time_str[:2]}:{time_str[2:4]}:{time_str[4:]}"
        return datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S").timestamp()

    match = re.search(r'Screenrecorder-(\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2})', filename)
    if match:
        datetime_str = match.group(1)
        formatted_str = f"{datetime_str[:10]} {datetime_str[11:13]}:{datetime_str[14:16]}:{datetime_str[17:19]}"
        return datetime.strptime(formatted_str, "%Y-%m-%d %H:%M:%S").timestamp()

    match = re.search(r'Camera_XHS_(\d{13})', filename)
    if match:
        return int(match.group(1)) / 1000

    match = re.search(r'^(\d{13})', filename)
    if match:
        return int(match.group(1)) / 1000

    return None

def check_exiftool_installed():
    try:
        subprocess.run(['exiftool', '-ver'], capture_output=True, text=True)
        return True
    except FileNotFoundError:
        return False

def fix_timestamps(directory):
    count = 0
    failed = 0

    if not check_exiftool_installed():
        print("Error: this script requires exiftool.")
        print("macOS: brew install exiftool")
        print("Linux: sudo apt-get install libimage-exiftool-perl")
        print("Windows: https://exiftool.org/")
        return

    for root, dirs, files in os.walk(directory):
        for filename in files:
            file_path = os.path.join(root, filename)

            if filename.startswith('.'):
                continue

            try:
                timestamp = extract_timestamp_from_filename(filename)
                if timestamp is None:
                    timestamp = run_exiftool(file_path)

                if timestamp:
                    current_time = time.time()
                    if timestamp > current_time:
                        timestamp = current_time

                    os.utime(file_path, (timestamp, timestamp))
                    print(f"Fixed: {file_path}")
                    count += 1
                else:
                    print(f"Could not extract a timestamp: {file_path}")
                    failed += 1

            except Exception as e:
                print(f"Failed to process: {file_path} - {str(e)}")
                failed += 1

    print(f"\nDone. Fixed: {count} files, failed: {failed} files")

if __name__ == "__main__":
    import sys

    if len(sys.argv) != 2:
        print(f"Usage: python {sys.argv} <path_to_DCIM>")
        sys.exit(1)

    dcim_path = sys.argv
    if not os.path.isdir(dcim_path):
        print(f"Error: '{dcim_path}' is not a valid directory")
        sys.exit(1)

    fix_timestamps(dcim_path)