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.
Now I can easily snag screenshots from one of my real computers to post.