Archiving Game Screenshots on Windows_

🇺🇦 Resources to help support the people of Ukraine. 🇺🇦
July 31, 2022 @14:40

Most video games these days have methods to save screenshots. Thanks to a complete lack of standards they end up all over the place. Adding in storefronts like Steam and GOG add to the mess by providing their own locations (some more hidden than others) you have to go searching for screenshots in. This is irritating if you want to go find your screenshots later. In what has become my tradition I wrote a Python script to fix this particular problem.

'''archive-screenshots.py (c) 2022 Matthew J. Ernisse <matt@going-flying.com>
All Rights Reserved.

Run periodically to copy screenshots from my Windows PC to the network
archive.

Redistribution and use in source and binary forms,
with or without modification, are permitted provided
that the following conditions are met:

    * Redistributions of source code must retain the
      above copyright notice, this list of conditions
      and the following disclaimer.
    * Redistributions in binary form must reproduce
      the above copyright notice, this list of conditions
      and the following disclaimer in the documentation
      and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
import configparser
import re
import shutil
import os

config = None
valid_images=[
    '.bmp',
    '.jpg',
    '.tga',
    '.tiff',
    '.png'
]

appid_re = re.compile(r'"appid"\s+"(\d+)"')
name_re = re.compile(r'"name"\s+"([^"]+)"')


def check_and_copy(src, dest):
    ''' Check to see if the file src is present in the directory dest and
    if it is not, copy it over.
    '''
    fn = os.path.basename(src)
    df = os.path.join(dest, fn)

    if os.path.exists(df):
        return

    shutil.copy2(src, df)


def process_directory(sdir, dest):
    for fn in os.listdir(sdir):
        s = os.path.join(sdir, fn)
        if not os.path.isfile(s):
            continue

        _, ext = os.path.splitext(fn)
        if ext not in valid_images:
            continue

        check_and_copy(s, dest)


def process_steam_screenshots(archive, config):
    ''' Look through all the installed Steam applicaitons to get an
    id to name mapping and then go through the obfuscated directory
    it puts the actual screenshots in to process them.
    '''
    steam_app_dir = os.path.expandvars(config['steamapp_dir'])
    steam_user_dir = os.path.expandvars(config['userdata_dir'])
    steam_user_id = config['steam_user_id']

    for fn in os.listdir(steam_app_dir):
        if not fn.endswith('.acf'):
            continue

        mfst = os.path.join(steam_app_dir, fn)
        with open(mfst, 'rb') as fd:
            appid = None
            name = None

            for line in fd.readlines():
                line = line.decode('utf-8')
                if match := appid_re.search(line):
                    appid = match.group(1)

                elif match := name_re.search(line):
                    name = match.group(1)

        if not appid or not name:
            continue

        dest = os.path.join(archive, name)
        src = os.path.join(
            steam_user_dir,
            steam_user_id,
            '760',
            'remote',
            appid,
            'screenshots'
        )

        if not os.path.exists(src):
            continue

        if not os.listdir(src):
            continue

        if not os.path.exists(dest):
            os.makedirs(dest)

        process_directory(src, dest)


if __name__ == '__main__':
    config = configparser.RawConfigParser()

    # Do not lowercase the options
    config.optionxform = lambda option: option
    config.read(os.path.join(os.environ['APPDATA'], 'screenshots.ini'))

    archive = os.path.expandvars(config['Global']['archive'])

    if 'Steam' in config.sections():
        process_steam_screenshots(archive, config['Steam'])

    if 'Games' in config.sections():
        for game in config['Games'].keys():
            src = os.path.expandvars(config['Games'][game])
            dest = os.path.join(archive, game)

            if not os.path.exists(src):
                continue

            if not os.path.exists(dest):
                os.makedirs(dest)

            process_directory(src, dest)

The script loads some configuration from %APPDATA%\screenshots.ini. If you have Steam configured it will discover the games you have installed and look in the right spot for screenshots taken using the Steam hotkey. You can also configure other locations manually. My configuration looks like this.

[Global]
archive = Z:\media\pictures

[Games]
Cyberpunk 2077 = %USERPROFILE%\Pictures\Cyberpunk 2077
Elite Dangerous = %USERPROFILE%\Pictures\Frontier Developments\Elite Dangerous
Star Citizen = G:\Roberts Space Industries\Star Citizen\StarCitizen\LIVE\ScreenShots

[Steam]
steamapp_dir = D:\Steam\steamapps
userdata_dir = %ProgramFiles(x86)%\Steam\userdata
steam_user_id=00000000000

By default Steam's steamapp and userdata directories are both in %ProgramFiles(x86)\Steam but I have Steam setup to install games to a different drive so my steamapp directory is different. In the Games section of the configuration you can set non-Steam locations to look for screenshots. The key is used as the name of the directory in the archive to copy the screenshots to and the value is the path to look for screenshots in. Any value gets environment variables expanded before processing. As an example Cyberpunk 2077 screenshots would copy files from C:\Users\my_username\Pictures\Cyberpunk 2077 to Z:\media\pictures\Cyberpunk 2077.

I threw the script into a scheduled task that runs daily.

Scheduled Task creation

Now I can easily snag screenshots from one of my real computers to post.

Comment via e-mail. Subscribe via RSS.