Opening Randomized Images From Multiple Folders With Preview Application

Fixing and rewriting the code, particularly in the repeat with aFolder in theFolders loop, and using a handler from the accepted answer by Lri in Is there a simple way to shuffle a List in AppleScript? to randomize the list over your method, this now works.

I tested it on several different folders individually as well as multiple folders together, with as many as 500 JPG files in a test run.

tell application "Finder"
    if not selection is {} then
        set theFolders to selection as alias list
    else
        return
    end if
end tell

set theImages to {}
repeat with aFolder in theFolders
    set getImages to "find " & aFolder's POSIX path's quoted form & " -iname '*.jpg'"
    set tempList to paragraphs of (do shell script getImages)
    repeat with aItem in tempList
        copy aItem to end of theImages
    end repeat
end repeat

set randomImages to shuffle(theImages)

set filePaths to {}
repeat with thisFile in randomImages
    copy (thisFile as POSIX file as alias) to the end of filePaths
end repeat

tell application "Finder" to ¬
    open filePaths using application file id "com.apple.Preview"


### Handlers ###

on shuffle(input)
    script s
        property L : input
    end script
    set i to count of L of s
    repeat while i ≥ 2
        set j to random number from 1 to i
        set {item i of L of s, item j of L of s} to {item j of L of s, item i of L of s}
        set i to i - 1
    end repeat
    L of s
end shuffle

Looks like I'm a bit late to the party, but here's another way to do it. I agree with you about Finder being slow (sometimes because it's not being used in the right way, but you've done everything right). It shouldn't be used for any filesystem operations other than ones that are unique to it (reveal, select, update) or for accessing properties and objects that are only available through Finder (selection, Finder window, original item, application file). It was actually superseded years ago by System Events, which took over it's filesystem and process inspection capabilities, but a lot of AppleScripters are stuck in their ways. System Events isn't quite as fast as the shell, but you ought not to notice an appreciable difference unless you're handling thousands of files at once. It is, however, approximately infinity times faster than Finder.


Firstly, I defined a script object just to house a couple of handlers and to improve speed for accessing list items:

script filepaths
    property selection : {}
    property list : {}

    to flatten(L)
        script
            property parent : L
            property index : 1

            on |ƒ|()
                set x to {} & item index
                if x = {} then return
                set [[item index], x] to ¬
                    [it, rest] of x
                if x = {} then return
                set the end to x
            end |ƒ|
        end script
        tell result to repeat until index > length
            |ƒ|()
            set index to index + 1
        end repeat
        L
    end flatten

    to shuffle(L)
        script
            property parent : L
            property index : my items
        end script
        tell result
            repeat with i from 1 to length
                set item i in the index to i
            end repeat
            repeat with i from 1 to length
                set k to some item in the index
                set [item k, item i] to ¬
                    [item i, item k]
            end repeat
        end tell
        L
    end shuffle

    to open
        tell application id "com.apple.Finder" to open ¬
            my list using application file id ¬
            "com.apple.Preview"
    end open
end script

There's nothing hugely complicated there. The flatten() handler will take a list of nested objects and return a flattened list. The shuffle() handler will shuffle a list in a reliably pseudorandom way that ensures every possible permutation for a list had an equal chance of arising. Some pseudorandom number generators have a slight tendency to favour items that don't sit at the boundaries.

That script object can go anywhere. Then the main part of the script looks like this:

tell application id "com.apple.Finder" to set the ¬
    selection of filepaths to the selection as ¬
    alias list

tell application id "com.apple.SystemEvents" to repeat with f in ¬
    (a reference to the selection of filepaths)
    if f's kind = "Folder" then set f's contents ¬
        to (the path of the files in f where ¬
        name extension = "jpg" and visible = true)
end repeat

tell filepaths
    set its list to every list in the selection
    flatten(its list)
    shuffle(its list)
    open
end tell

It uses Finder only to grab the selected files; it uses System Events to filter the selection so that only folders are considered, and then it reads the contents of those folders and mutates the list to contain the jpg files in those folders.

This will be a nested list, which would be fine to open in Preview without further processing, but in order to shuffle it, it first needs to be flattened. So that's done, then the order is randomised, then they're opened in Preview.


I tested this with a few hundred images in a dozen folders, and it was blindingly fast up until I stupidly opened them all in Preview, at which point I regretted that. Hopefully you have a more powerful machine than I, because mine is now roasting hot and requires a restart.