holwech / WhatsAppBackupFixer

Fixing restored WhatsApp image backups
MIT License
20 stars 5 forks source link

Issue with modifying pictures and only the 'modified' date gets changed instead of the 'made' date as well. #4

Open Aqqly opened 2 years ago

Aqqly commented 2 years ago

Hi @holwech, thank you for WhatsApp Backup Fixer to begin with.

I understand how your script works and what it does, but I'm certain that there's something wrong with it. I did multiple reproduction steps. The issue is that the dates of WhatsApp images don't get changed properly, once I run the script it sets the 'modified' date of said image(s) to the current date (so, when the script ran). With WhatsApp videos it does change the 'modified' date to the one it retrieves from the filename, but it also doesn't change the 'made' date.

So, I would like to ask you if 1) you can fix that the date gets changed for the pictures, that seems totally broken right now and 2) if you can make it so that it modifies the 'made' date instead of only the 'modified' date. Most mobile phones, and please correct me if I'm wrong, will sort the pictures by 'made' date rather than 'modified'. At least my phone does and others that I know of. Due to this it'll still be messed up and take away the meaningfulness of this script.

I'm on Windows 11 by the way, I've also tried BATCHIFIER, it changes the dates of pictures too but also only the 'modified' date.

dariustanrd commented 2 years ago

@FrappuccinoCaramel I just faced the same problem.

You can replace the following:

original get_date function to:

def get_date(filename):
    date_str = filename.split('-')[1]
    date_datetime = datetime.strptime(date_str, '%Y%m%d')
    date_str = date_datetime.strftime("%Y:%m:%d %H:%M:%S")
    return date_str, date_datetime

original elif block to:

elif filename.endswith('jpg') or filename.endswith('jpeg'):
    date_str, date_datetime = get_date(filename)
    zeroth_ifd = {piexif.ImageIFD.DateTime: date_str} # ModifyDate (does not actually change, since this script itself modifies the image)
    exif_ifd = {piexif.ExifIFD.DateTimeOriginal: date_str, # date/time when original image was taken
                piexif.ExifIFD.DateTimeDigitized: date_str # CreateDate
                }
    exif_dict = {"0th":zeroth_ifd, "Exif":exif_ifd}
    exif_bytes = piexif.dump(exif_dict)
    piexif.insert(exif_bytes, file_path)
    # change modify date manually
    utime = time.mktime(date_datetime.timetuple())
    os.utime(file_path, (utime, utime))
Aqqly commented 2 years ago

Hi @dariustanrd, thank you so much for sharing your recommended solution!

I've edited the script and replaced that function and block with your code in the same indentation style. When I tried running it the following error came, unfortunately:

Traceback (most recent call last):
  File "D:\Users\-\Downloads\WhatsAppBackupFixer\test_images\fix_exif.py", line 35, in <module>
    date_str, date_datetime = get_date(filename)
ValueError: too many values to unpack (expected 2)

I'm not really into Python so I likely won't be able to fix it myself.

dariustanrd commented 2 years ago

@FrappuccinoCaramel here's the full script that worked for me!

I added more filetypes (audio, docs, stickers) as i realised WhatsApp's naming convention for them are similar too.

from datetime import datetime
import piexif
import re, os, time

folder = './'

def get_datetime(filename):
    date_str = filename.split('-')[1]
    date_datetime = datetime.strptime(date_str, '%Y%m%d')
    return date_datetime

def get_date(filename):
    date_str = filename.split('-')[1]
    date_datetime = datetime.strptime(date_str, '%Y%m%d')
    date_str = date_datetime.strftime("%Y:%m:%d %H:%M:%S")
    return date_str, date_datetime

fn_regex = re.compile(r'(IMG|VID|AUD|DOC|STK)-(\d{8})-WA.*\.(jpe?g|mp4|3gp|aac|m4a|opus|mp3|pdf|xlsx|webp)')

filenames = [fn for fn in os.listdir(folder) if re.match(fn_regex, fn)]

num_files = len(filenames)
print("Number of files: {}".format(num_files))

for i, filename in enumerate(filenames):
    file_path = os.path.join(folder,filename)
    if filename.endswith('mp4') or filename.endswith('3gp') or \
        filename.endswith('aac') or filename.endswith('m4a') or filename.endswith('opus') or filename.endswith('mp3') or \
        filename.endswith('pdf') or filename.endswith('xlsx') or \
        filename.endswith('webp'):
        date_datetime = get_datetime(filename)
        # change modify date
        modTime = time.mktime(date_datetime.timetuple())
        os.utime(file_path, (modTime, modTime))

    elif filename.endswith('jpg') or filename.endswith('jpeg'):
        date_str, date_datetime = get_date(filename)
        zeroth_ifd = {piexif.ImageIFD.DateTime: date_str} # ModifyDate (does not actually change, since this script modifies the file)
        exif_ifd = {piexif.ExifIFD.DateTimeOriginal: date_str, # date/time when original image was taken
                    piexif.ExifIFD.DateTimeDigitized: date_str # CreateDate
                    }
        exif_dict = {"0th":zeroth_ifd, "Exif":exif_ifd}
        exif_bytes = piexif.dump(exif_dict)
        piexif.insert(exif_bytes, file_path)
        # change modify date
        utime = time.mktime(date_datetime.timetuple())
        os.utime(file_path, (utime, utime))

    num_digits = len(str(num_files))
    print("{num:{width}}/{max} - {filename}"
            .format(num=i+1, width=num_digits, max=num_files, filename=folder+filename))
print('\nDone!')
Aqqly commented 2 years ago

@dariustanrd Thank you so much! I ran your edit of the code without any problems, it did just like you said it would. Great work. Maybe it would be nice to create a PR, because I feel like your changes are literally what the original code should've been.

caweidmann commented 2 years ago

For me the video part didn't work. Leaving this here in case others bump into this.

In order to get video dates fixed (Windows only solution) I did the following:

pip install win32-setctime

Then I modified the script to the following:

# right at the top of the file
from win32_setctime import setctime

and directly below the following code:

os.utime(folder + filename, (modTime, modTime))

add the following line:

setctime(folder + filename, modTime)

Then I ran the script, then I had to zip up the files, copy the zip to the phone and then extract the files (because copying them over just like that caused the "created" date to get changed).

holwech commented 2 years ago

Very nice @dariustanrd! If you want, you can create a PR and I'll merge your changes and add a mention of your contribution in the readme.