Category PS4 CFW and Hacks       Thread starter PSXHAX       Start date Jul 13, 2019 at 1:25 AM       1,306       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
PlayStation Store Flash Sale Offers Up to 75% Off PSN Games
Summer is heating up with record high temperatures and some hot PSN deals on select PlayStation Store titles featuring PS4, PS3 and PS Vita games in the mix! ☀ 🔥 🥵 🍹 Below is the full lineup of...
The Diamond Casino & Resort Hits GTA Online on PS4 July 23rd
Proceeding the opening of the GTA Online PS4 After Hours Nightclub and GTA V PS4 Glitches discovered, today RockStar announced that the Diamond Casino & Resort grand opening will be on July 23rd...
Dishonored: Definitive Edition 60 FPS Mod PS4 PKG by Wastelander121
Following their Batman: Arkham Knight Free Roam Mod Menu port, today @Wastelander121 (YouTube Channel) released on Twitter a Dishonored: Definitive Edition 60 FPS Mod PS4 PKG (CUSA-02230) for...
PS4 System Software / Firmware 6.72 Released, Don't Update!
Just over a month ago Sony released a PS4 OFW 6.71 Update followed by a few 6.80 Beta Updates for those in their PlayStation Preview Program, and today another PS4 System Software / Firmware 6.72...