Home and Links
 Your PC and Security
 Server NAS
 DVD making
 Raspberry Pi
 PIC projects
 Other projects
 Next >>

CMD Script for Windows Photo Frame

CMD script

Overview of a typical Photo Frame script

Note - the main reason for 'scripting' your PhotoFrame is to fetch images to a local 'cache' in order to minimise network traffic. If network loading is not a problem for you, all you really need to do is 'point' the 'Screen Saver' (on the PhotoFrame) at the photo archive 'share' (on the Server) and it will fetch (and resize) photo's automatically.

If you continue as below, note that the Screen Saver must be 'pointed' at the local cache 'M:'. Since the Screen Saver will likely 'abort' if M: does not exist, it is VITAL that M: is created by the script before the Screen Saver 'cuts in'. You thus need to set your script to run immediately after power-on and make sure the Screen Saver 'waits' some time (at least a few minutes) so it doesn't 'cut in' straight after power-on :-)

To generate the script, I start with a general 'specification' (statement of the goal) and then turn this into a 'high level' description of what needs to be done to achieve that goal. With a complex task I would then split the description into 'modules' and generate at least one further level of detailed description, however this script can be 'done in one' so I go straight to the detailed design (pseudo code)

The Specification (overall goal)

The goal is to load a RAM disk cache with a display sized version of as many NEW photo's as possible. So loading will start with the 'newest' photo and 'loop', working backward to older ones, until we run out of 'newer' photos.

To make way for new photo's, when space starts to run out, older ones already in the cache must be deleted, starting with the oldest first & then 'loop' checking free space again until enough free space is found for another new image or the last old image is deleted.

With all 'looping' type scripts, there is a danger that a bug might result in it running 'for ever' or 'locking up' & failing to complete correctly. It is even possible for a second 'instance' of the script to be started before the first completes. This means LOTS of checking at each step to make sure everything happens as expected.NB. One way to 'mark' files eg as 'old' or 'new', is to use the file Attributes - for example, the ARCHIVE bit, however I order the files by date/times and name one as "end.stop" instead

Detailed Description (top level)

(1) Start by checking if the cache exists and, if not, (2) create it and add an old file

(3) Locate the 'newest' (or only) file in the cache, make it the 'end stop' & and save it's date

(4) Go to the photo archive and get a folder listing, in date order, newest first.

(4a) Step through the folders checking if the folder is newer than the saved date - if so, process that folder as below

(6) Get a listing of the .jpg photos in a newer folder, in date order, newest first

(6a) Step through the photos comparing their dates with the 'end stop'

(7a) If the archived photo is newer, add it to the cache
(when the last photo in this folder has been processed, return to process the next folder)

(8) Check the free space in the cache, if there is plenty return to process the next photo
(9) Delete the current oldest file to make space
(10) Unless 'end stop' has been deleted, Loop back to recheck the free space

Actual Design (pseudo code)

() means see notes for this step
1. Set some 'constants' (Server IP, user name, password etc) & create the Temp: RAM disk
2. Check if the M: cache exists, if so skip to (3)
(2a) Create the cache RAM disk. It can't be empty, so load in any ancient jpg file(3) Locate newest file in M:, copy it to 'end-stop.jpg' and find it's date
4. Map the Server Photo archive share & get a listing of the archive folders, newest first
4a. FOR each archive folder, call (5)
(4b) All done, now sort the 'time stamp problem' (see later) & clear-up (delete end stop, delete temp files etc) & exit(5) If the folder date is newer than 'end stop', return (to 4a)
6. Get a listing of the (*.jpg) photo's in this folder
6a. FOR each photo, call (7)
6b. (after the last photo has been processed, return (to 4a)(7) Copy the photo to Temp
(7a) If the photo is 'newer' than end-stop, make it's thumbnail, add that to M: (& remove photo from Temp)8. Check the remaining space in M:
8a. If there is plenty of space, return (to 6a)
9. Delete the oldest file in M:
9a. If end-stop has been deleted, goto 4b
10. Recheck the space (goto 8)

Notes on above (2a, 3) - the newest file is used as an 'end stop' = this is so I can do simple date 'compares' (using DIR to get a date order listing)
(4b) - see 'time stamp problem' below (note - the quickest way to 'delete temp files' is delete the entire T: temp RAM disk)
(5) - this is easy because I name my photo archive folders starting with the date (YYYY-MM-DD)
(7) - the photo is copied from the archive server into Temp to 'decouple' the resizing process from the network transfer - this makes it a LOT easier to debug :-)
(7a) - any recently modified photo will be 'out of step' HOWEVER it can never get 'older' than the original, so won't cause the cache loading to stop 'early'
(9) The script halts (after 4b) when the folder list is exhausted (at 4a) or because the 'end stop' FILE is deleted

What's the 'time stamp problem' ?

When photo's are processed and saved as display sized thumbnails, the thumbnails get new dates. Whilst all thumbnails created 'today' will get 'todays' date, the 'find oldest to delete' DIR listing will take into account the time as well. Since we start today with the 'newest' photo, it's thumbnail goes into the cache first and will have the 'oldest' time stamp of all today's thumbnails (and the last we process today - oldest image - will get the newest time).

As a result, when we delete the 'oldest' to make way for 'tomorrows' newer photo's, it will be the thumbnail of today's newest image that is deleted first. Of course, eventually (tomorrow & tomorrow) all of todays will be replaced, however this may not be what you are expecting

To ensure the oldest thumbnail will be deleted first, the 'time stamp' order must be reversed at the end of todays processing. COPY and RENAME won't change the date, however 'TYPE'ing does. So TYPE each new thumbnail created 'today' in 'reverse date order' to create a replacement file and this will 'reset' the time stamps into the 'correct' order

Needless to say, this trick should NEVER be played on a cache held directly on a DOM / CF card / USB stick (or you will run into the life-time write limit twice as fast !)

Can I have your script to work with ?

Yes, download LoadCache.cmd here

Note - when writing a script and using 'IF' statements to control program flow, it is GENERALLY 'a good idea' to 'action' on a 'success' of the IF (rather than on 'fail' or the 'success' of IF NOT). This is because the 'IF' command will 'fail' for all sorts of reasons until fully debugged and it's a lot easier to debug when the action fails (and for 'IF' --> delete, you will want it to 'fail safe')

Do you have any additional notes on the script ?

A1. To save power, you may wish to allow the photo-frame to 'hibernate' or power-off. If the photo-frame has a hard disk, 'hibernate' will save the RAM disk contents automatically. If you power-off, use the Vsuite Ramdisk (free ed) to setup M: from within Windows and use the 'save & restore' feature to save M: at power off and load at power-on.

Saving the RAM disk contents will avoid massive network traffic when you turn on your photo-frame each morning - even saving to a network share will reduce the loading by up to 8x (depending on the original photo size and the new display 'thumbnail' size), since, instead of fetching 16Gb of hi-res photo's you will be 'saving' only 1Gb of thumbnail display resolution images and restoring that same 1gb later (reducing a 16Gb transfer to 2 x 1Gb)

A2. If the photo-frame has limited RAM, the cache may be setup into a CF card or USB Memory stick. Since all 'write' operations to M: must be minimised, the script always uses a separate 'Temp' RAM disk (even if the cache is in RAM)

If you have lots of RAM (for M:) and the photo-frame is running from DOM, chances you have installed MS EWF (Enhanced Write Filter) and the DOM is too small to accept Gb's of RAM at hibernate anyway. However, if you fit a CF card as a D: 'drive', you can still use Vsuite Ramdisk to 'save' at power-off (& restore on power-on)

A3. The network 'map' to the archive folders is dropped on exit from the script. This is done to 'hand back' the Client Licence to the Server. This means there is a possibility that a subsequent 'map' will fail due to insufficient licences (hence the IF NOT EXIST P:\NUL test).

A4. Start with a small cache - say 50Mb. This lets you debug all the way to the 'delete to make space' and 'end clean up' code without having to wait hours whilst a 1Gb cache is loaded

A5. The RAM disk driver, imdisk, uses the DOS 'format' command to perform the actual format operation. If you are using a nLite cut down XP installation, and deleted DOS support to save space, you will need to add back 'format.exe'

A6. My photo archive folder names always start with the year-month-day and then some arbitrary text (eg '2002-05-07 May - last day in Innsbruck'). So DIR /o-n (= named order) will list my folders in the 'newest' date when the photos were taken. If you use arbitrary folder names, you will have to use folder creation date (DIR /o-D). Note the /ad parameter that lists folders ('directories') only

Why do I name my folders with the date the photo's were taken ??? = because this makes it a LOT easier to locate the photo's taken eg. on the last day of my May holiday in Austria 10 years ago, rather than trying to remember exactly where I was on that day (or whatever other name I might have used for the archived folder)If you rely on folder 'creation' dates this will be days or weeks after the photo's were taken - or worse, years later, when you decide to split a folder into 2 parts or combine two into one etc.

A6. I always check the archive folder date before checking the actual photo's. If no new folders have been created, this avoids forcing the Server disks to 'spin up' - which they won't so long as the folder is not 'opened' (the 'top level' folder Directory will be cached in Server RAM)

Since I list the folders in date order, I can 'cut short' date checking as soon as the first 'not newer' folder is found - however I do still step through all the folders in the list. To avoid processing the whole folder list, separate code could be added, before 'DO folders' to check if the newest folder (top of list) is newer than the last cache date

A7. The script copes with an empty cache by adding in a 'dummy' old file to act as an 'end stop'. The script also copes fine with running out of archived photo's before the cache is full

A8. Some camera's will set the Photo's EXIF flags to indicate a 'portrait' shot. If yours is one such (and you archive your photo's un-rotated) you may wish to add code to detect any photos that need to be rotated prior to display. I avoid this problem by manually rotating the .jpg (but not the RAW), as needed, before archiving my photo's.

Any rotate must be done AFTER checking that the photo in the Temp store is 'newer' but BEFORE doing the resizing into M: (this will mean splitting my "IF (newer than newest) then resize into M:" statement)

A9. For those who need a few 'hints' on the syntax of any specific commands, I recommend this very good Windows NT CMD command reference website

A10. A shortcut to your script should be added to the Windows 'Programs / StartUp' folder.

If the photo-frame is left on all the time, the script should be added to the 'Scheduled Tasks' list and set to run eg. every Saturday, Sunday and (say) Wednesday in the evening, after you have 'archived' any new photo's taken that day

The MS Scheduled Tasks 'wizard' is a bit restrictive, but you can set up multiple 'events' to get what you want. Better would be to use an alternative Task Scheduler)

You can use Task Scheduler to power off the PhotoFrame after 12 hours & the BIOS to turn on again each day

To power off at a specific time, you can use the Task Scheduler to run SHUTDOWN.EXE. To power on, go into your Motherboard BIOS & find the 'Automatic Power Up' feature (which, if enabled, lets you designate a specific time each day when the PC will turn itself on)Since the script will run at power-on, it will always fetch any new photo's (even if the cache was saved to hard disk or CF card etc. & restored at power-on) & then set the new shutdown.exe time
The length of the delay before XP shutdown.exe is in seconds and can be set to > 12 hours. To set shutdown.exe to power-off after 12 hours, add "shutdown -s -t 43200" to the end of the script that runs at power on (note - the script has to run at Administrator (or System) account level)

Your script seems over-complicated / over simplified ?

A1. I have tried to make the script as simple as possible, however CMD's lack of any 'built in' support for things like file date and folder size comparison does make the code somewhat convoluted. Generally, the simpler the code the easier it is to 'debug' - vital when the script will be handling thousands of images and Gb's of data and deleting files to make space.

You will notice that I always use the full drive path when creating or deleting files. This is to prevent me accidentally deleting original photo's or filling my archive with Gb's of PhotoFrame thumbnails whilst debugging the script

Anything else ?

A1. I have already stressed the need to 'map' to your archive using a 'read-only' User account - during script debugging this may be the only thing that prevents a faulty script deleting thousands of your archived photo's (instead of the local 'cached' version) !

A2. To avoid the need to go back to the PhotoFrame (and add a keyboard & turn off EWF) when I want to make changes to the script, I placed a 'stub' script on the PhotoFrame that fetches ('chains') the 'real' script from the archive server 'share' every time it is run

A3. If your PhotoFrame CPU is very low speed and/or you have minimal cooling (or even no fan) you might wish to avoid actually running the script (and image resizing software) on the actual PhotoFrame

Instead you can run the script on the Server - and have it 'push' display sized images into the PhotoFrame cache. This has the advantage of reducing network loading by about 16x BUT means you have to give the PhotoFrame 'execute' rights on some part of the Server.

If you do so, you need to take extreme precautions to avoid a script error deleting your archive (for a start, the script must be in a different 'share' so that the PhotoFrame User can still be restricted to 'Read Only' in the Photo Archive)

A4. Instead giving the photo-frame the 'right' to run a script on the server, the PhotoFrame could simply make it's cache 'available' on the network. A script already running on the server (or some other computer) could then 'push' ready sized display images into the photo-frame cache when-ever they wanted to

The drawback is that, unless the PhotoFrame has a hard disk etc. to which it can 'save' the cache at power-off, the cache will be empty when the PhotoFrame is first turned on. This means the Server (or some other computer) has to keep 'checking' for a just powered-on photo-frame so it can start to load images to be displayed. Checking every couple of minutes will stop the server ever going to sleep, although it may be possible to setup the photo-frame so it 'wakes up' the Server from LAN (and thus 'poke' it for images), however now we are getting very clever ...

A5. A separate script that runs when a CF card or USB memory stick is inserted (see below) allows the PhotoFrame to be used 'away from home' (i.e. when it has no access to the server). This script is very much simpler, since you can assume you want to see 'all' the images on a just inserted USB stick, so there is no need to check for 'older' / 'newer'

A6. To avoid all the 'mucking about' with cache size checks wouldn't it be nice to have Windows automatically delete the oldest to make way for the newer ? Well, this is essentially what the Recycle Bin does .. so why not move the 'cache' to the Recycle Bin and set it to use 100% of the RAM disk ?

Unfortunately I haven't quite managed to get a 'recycle bin' cache to work the way I want it = perhaps you will have better luck !Windows records the time when a file is deleted to the recycle bin. When the bin becomes full, and you delete (add) another file to the bin, Windows removes the 'minimum' number of 'oldest deleted date' files to make room - i.e. it SIZE CHECKS what's in the bin and will removes one big file instead of 2 or more smaller ones.This means Windows does not always remove the one that's been in the bin the 'longest', especially as 1024x1280 jpg photo's vary from 100kb to 1Mb, although if you place the 'oldest' photo images in the bin first they will eventually be removed to make way for 'newer' ones

How do you deal with USB / CF etc. cards being inserted ?

On 'insert detect' you run a script to copy any images found on the CF/USB to the display cache.

The script wipes the display cache and then copies jpg's from the root, 1st level folders and second level folders of the USB device. Since I have a 1Gb M: cache I have to waste time worrying about checking space. Further, since existing thumbs are wiped, the new photo's will appear (be shown by the Screen Saver) 'immediately'.Of course, if some-one does turn up with a 32Gb USB / CF etc. full of images, the script will 'crash' when space runs out

Outline script to RUN when a CF card is inserted to 'D:' is seen :-

Setlocal EnableDelayedExpansion
:: Confirm existence of D:
:: create a Temp RAM disk, 25Mb should be enough
imdisk -a -s 25M -m T: -p "/fs:fat /q /y"
:: clear the display cache M:
:: note - if the new device in D has no images on it, the display will just go blank
DEL /q M:\*.jpg
:: get the root folder names
DIR D:\ /o-n /ad /b > T:\aFolders.lst
FOR /f %%G IN (T:\aFolders.lst) DO (
::check for 2nd layer folders
DIR D:\%%G /o-n /ad /b > T:\bFolders.lst
IF EXIST T:\bFolders.lst (
FOR /f %%H IN (T:\bFolders.lst) DO (
:: process any jpgs in the 2nd level folders
DIR D:\%%G\%%H\*.jpg /o-D /a-d /b > T:\temp.lst
IF EXIST T:\temp.lst FOR /f "tokens=*" %%I IN (T:\temp.lst) DO convert D:\%%G\%%H\%%I -resize 1280x1024 M:\%%I.jpg
:: now process any jpg's in the first level folders
DIR D:\%%G\*.jpg /o-D /a-d /b > T:\temp.lst
IF EXIST T:\temp.lst FOR /f "tokens=*" %%H IN (T:\temp.lst) DO convert D:\%%G\%%H -resize 1280x1024 M:\%%H.jpg
:: finally process any jpg's in the root
DIR D:\*.jpg /o-D /a-d /b > T:\temp.lst
IF EXIST T:\temp.lst FOR /f "tokens=*" %%G IN (T:\temp.lst) DO convert D:\%%G -resize 1280x1024 M:\%%G.jpg
:: OK thats it
:: delete the temp RAM disk
imdisk -D -m T:

End of D: check. You will see I've added the 'usual' extra '.jpg' to the resized file name (so you can swap the time order of new files)

What about WiFi ?

By all means fit a WiFi 'dongle' or WiFi PCI card to your photo-frame. The 'script' does not care how Windows is 'connecting' to the Server share, just so long as the 'share' is accessible by 'drive letter'.

Clever people can have fun modifying the script to 'search' for any nearby WiFi (or even Bluetooth) devices and accessing their 'hidden' shares (C$ etc) using the default Windows 'simple file sharing' (and 'Guest' account) to copy any .jpg's found

For my next Project (a Ring Flash) Click 'Next >>' in the Navigation Bar (left)

Next subject :- Ring flash lamp - (project)