Category PS4 CFW and Hacks       Thread starter PSXHAX       Start date Jul 13, 2019 at 1:25 AM       1,695       1            
Following the Final Fantasy XIV: Stormblood and recent Final Fantasy XIV: Shadowbringers Final Fantasy XIV Online PS4 expansion pack comes a FFXIV PS4 Screenshot Retimer Python Script by Skydeo to correct the file timestamps when transferring in-game screenshots from a PS4 to a flash drive. 📸 🖼

Download: / GIT

Below are further details from the, to quote: PS4/FFXIV Screenshot Retimer

I play a lot of FFXIV on the PS4, and take a lot of screenshots when doing so (over 5000 by the end of 4.0!). A few times a year I'll transfer these off my PS4 to save space and help me look at them, but while the datetime stamps are recorded in the file name¹, they aren't stored in the file metadata, instead just being whenever files were moved to a flash drive.

This means the screenshots don't sort correctly and it drives me crazy. How can I relive my adventure through Eorzea when the screenshots are out of order? This fixes the metadata using the datetime stored in the filename.

Example usage:
python ~/Desktop/PS4/FFXIV/Screenshots/ -e
I settled on using the -e flag to actually execute, but that might be a little overly cautious. There are a couple of other optional parameters you can read about using the -h flag (python -h).

There are two ways it assigns times, a fast method which works most of the time but doesn't assign file creation times on macOS² for some edge cases, and a slow method that does but requires the installation of macOS Developer Tools. Chances are these are already installed for most targets of this script.
  • ¹ 'Stored' in a non-sortable way using the stupid US MM-DD-YYYY notation.
  • ² I guess most UNIX systems don't care about file creation time, on access and modified times. The file creation time will be set if the modified time is before present.

Make it work with videos. For some unknown reason, there are at least 3 different formats of video file names over the years. Shouldn't be hard, but need to add flags and test.
import os
import re
import time
import timeit
import shutil
from pathlib import Path
from gooey import Gooey, GooeyParser

def retime_ps4_screenshots(input_dir, execute=False, rename=False, setfile=False, verbose=True):
  start_time = timeit.default_timer()
  input_dir = Path(input_dir)

  def create_dir_list(input_dir):
    p = Path(input_dir).iterdir()
    dir_list = [entry for entry in p
                if entry.is_file()
                and not'.')]
    return dir_list

  def correct_file_time(filename, datetime_string):
    time_struct = time.strptime(datetime_string, '%m/%d/%Y %H:%M:%S')
    seconds_since = int(time.mktime(time_struct))
    os.utime(filename, (seconds_since, seconds_since))

  def correct_file_time_setfile(filename, datetime_string):
    os.system('SetFile -dm "{}" {}'.format(datetime_string, filename.replace(' ', '\\ ')))

  if setfile:
    if not shutil.which('SetFile'):
      print('Creation time flag (-c) was used, but SetFile command cannot be found.')
      print('Install Xcode command line tools from')
    elif verbose:
      print(f'SetFile command found: {shutil.which("SetFile")}')

  # path = os.path.expanduser(input_dir)
  if verbose:
    print(f'Checking directory {input_dir}')
  dir_list = create_dir_list(input_dir)

  files = []
  videos = []
  for item in dir_list:
    if item.suffix.lower() in ['.jpg', '.png']:
      files.append(re.split(r' |_|\.', + [item])
    elif item.suffix.lower() == '.mp4':
      videos.append(re.split(r' |_|\.', + [item])

  ['Xander', 'Barabroda', '06', '18', '2017', '13', '37', '51', 'jpg']
  0 = First name
  1 = Last name
  2 = Month
  3 = Day
  4 = Year
  5 = Hour
  6 = Minute
  7 = Second
  8 = extension

  # Validate the files to build images and invalid files lists.
  images = []
  invalid_files = []

  if verbose:
    print('Checking files for invalid file names and extensions.')
  for f in files:
    dir_entry = f[-1]
    if len(f) != 10:
      if verbose:
        print('Invalid filename: \'{}\''.format(dir_entry))
    elif dir_entry.suffix not in ['.jpg', '.png', '.mp4']:
      if verbose:
        print('Invalid extension: \'{}\''.format(
  if verbose:
    if not len(invalid_files):
      print('None found.')

  if verbose:
    print('Retiming images.')

  for i in images:
    [first, last, month, day, year, hours, minutes, seconds, extension, original_filename] = i

    time_string = f'{month}/{day}/{year} {hours}:{minutes}:{seconds}'
    time_string_sane = f'{year}/{month}/{day} {hours}:{minutes}:{seconds}'
    new_filename = input_dir / f'{first} {last} {year}{month}{day}_{hours}{minutes}{seconds}.{extension}'

    if verbose == 2:
      print('\tRetiming to {}{}'.format(time_string_sane, '\n' if not rename else ''))

    if execute:
      if setfile:
        correct_file_time_setfile(original_filename, time_string)
      correct_file_time(original_filename, time_string)

    if rename:
      if verbose == 2:
        print('\tRenaming to \'{}\'\n'.format(new_filename))
      if execute:
        os.rename(original_filename, new_filename)

  end_time = timeit.default_timer()
  print('Completed in {0} seconds.'.format(round(end_time-start_time,2)))
  print('{} files {} {}{}.'.format(len(images), 'were' if execute else 'will be', 'renamed and retimed' if rename else 'retimed', ' using SetFile' if setfile else ''))

  if len(invalid_files) > 0:
    print('\nThe following {} {} {} processed. {}.'.format(len(invalid_files),'files' if len(invalid_files) != 1 else 'file','weren\'t' if execute else 'won\'t be', 'See above' if verbose else 'Run with the -v flag for more details'))
    for invalid_file in invalid_files:

  if not execute:
    print('DRY RUN COMPLETE. No files were modified. Run with -e flag to execute.')

def main():
  parser = GooeyParser(description='Correct the file creation and modified time on FFXIV screenshots exported from the PS4.')
  parser.add_argument('directory', metavar='Screenshot Folder', widget='DirChooser', help='Path to the directory containing the images.')
  parser.add_argument('-e', '--execute', action='store_true', help='Modifiy the files.')
  parser.add_argument('-r', '--rename', action='store_true', help='Rename the files to \'First Last\'')
  parser.add_argument('-c', '--creation_time', action='store_true', help='Use the macOS SetFile command to also set file creation time. It\'s much slower!')
  group = parser.add_mutually_exclusive_group()
  group.add_argument('-v', '--verbose', action='store_true', help='Display additional logging.')
  group.add_argument('-n', '--noisy', action='store_true', help='Display a huge amount of logging.')

  args = parser.parse_args()

  if args.noisy:
    args.verbose = True


if __name__ == '__main__':
Final Fantasy XIV (FFXIV) PS4 Screenshot Retimer Script by Skydeo.jpg


Recent Articles
Simple Wireless Rover for Raspberry Pi Controlled by PS4 DS4 via WiFi
Following the DJI Tello Drone and DeepRacer RC remote control PS4 DualShock 4 mods, recently Veilkrand on Github shared a Simple Wireless Rover for Raspberry Pi Controlled by PS4 DS4 via WiFi for...
Capcom Home Arcade Launches October 25th, Details and Trailer Video
Previously we covered the RetroEngine Sigma and Game Box Hero systems for emulation fans, and recently Capcom announced their Capcom Home Arcade launches this October 25th with pre-orders...
PS4 Retail Theme Unlocker Windows GUI Front-End by Backporter
Proceeding the PS4 DLC, Games, Updates & Themes Guide by @AluPL (aka TheRadziu on Twitter) today @Backporter shared via Twitter a PS4 Retail Theme Unlocker Windows GUI front-end...
PS4 Games Up to Half Off as PlayStation Plus Platinum Weekend Begins
This weekend PlayStation Plus members can save up to 50% off select PS4 games during Sony's PS Plus Platinum Weekend Sale with titles including Days Gone, Grand Theft Auto V, Rage 2 Deluxe Edition...