0

Basically what I am trying to achieve is the following:

  1. Create an alias list of any amount of folders with the Finder's selection.
  2. Get all images from those folders with a shell script.
  3. Randomize those images.
  4. Open the randomized images in the Preview application.

The script I created is partially working with two selected folders that each contain three images (6 total). But it is only opening 3 images with the preview app instead of all 6.

I am also trying it out with more than 100 images in two separate folders and it gives me an error:

"Can’t make file \":43\" into type alias." number -1700 from file ":43" to alias

Also, the images are not being randomized every time I execute the script.

Please note that I prefer using the shell script to fetch the images because it is much faster than using the Finder. If it is possible to also use a shell script to randomize the images, I will like to know how this can be achieved in the script I created.

Any help is appreciated. Thanks!

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

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

        -- Randomize selected images

        set randomImages to {}
        repeat with thisImage in some item in theImages
            set end of randomImages to thisImage
        end repeat

        -- Get aliases

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

        -- Open random images with Preview

        open filePaths using application file id "com.apple.Preview"

    end if
end tell
  • I find that the first set of images in theImages is overwritten by the second rather than being added to. – Mockman Apr 11 at 14:08
1

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
| improve this answer | |
  • When I focus just on the section before 'set randomImages…', I get each file repeated three times (which was why I put aside the shell script). Obviously it's very fast but are there no negative implications due to the repetitive results? And thanks for the reference to the list shuffle answer. It's a good read. – Mockman Apr 11 at 23:27
  • Wow! 500 images in 3 seconds. Unbelievable. This should work for me. I guess I will have to learn how to use handlers. Thanks for pointing out how to shuffle with AppleScript and thanks to Mockman for the effort. – David Apr 11 at 23:45
  • @Mockman, RE: "When I focus just on the section before 'set randomImages…', I get each file repeated three times (which was why I put aside the shell script)." --- Without seeing what's you are seeing I'm not sure I can say anything other then I only get the correct file count for any folder/folders selected as there are JPG files within the selected folders. I typically do through testing before I post code. :) – user3439894 Apr 12 at 0:13
  • @Mockman, The only thing I don't like about this code, and it was there before I touched it, is the double-slashes in the POSIX filenames e.g. ../Folder//Filename.JPG, and I know why it's happening and how to fix it, but didn't what to add the additional code since it probably wasn't going to hurt things anyway. – user3439894 Apr 12 at 0:13
1

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.

| improve this answer | |
  • Yes I just realized that opening all of those images in Preview is not a good idea. I actually used Preview as a place holder so in the future I can replace it with any other application. I see your script is also very fast. Thank you. – David Apr 12 at 11:41
  • For some reason, on my computer it is only pulling up a portion of the total images. Any idea as to why that is the case? Out of 500 images it only opens about 140. – David Apr 12 at 12:02
  • Are your sub-folders nested ? Currently, this only grabs images in the selected folders, but not any images in any folders contained in the selected folders. I can adjust it to do a recursive deep dive if you need that. – CJK Apr 12 at 18:44
  • There are no sub folders just regular folders. I have had this issue not only with your script but with other as well and I am still trying to figure out why. – David Apr 13 at 11:08
  • Hm, weird. Does that happen with those specific sets of folders and files, or with all folders elsewhere on your filesystem ? If the folders that are selected only contain files, or contain files as well as other folders but that you aren't needing to retrieve files insides those other folders, then I don't know why it would be returning only some of the files one level below your selection on your computer and not on mine. Are you running Catalina or Mojave ? – CJK Apr 14 at 6:07
0

I know that you asked for a primarily shell script solution but I had challenges trying to generate a flat list of files using 'find'. I do have an all applescript solution though.

tell application "Finder"
    set theFolders to selection as alias list

-- Create list of jpegs within selected folders
    set fColl to {}
    repeat with eachSet in theFolders
        set imgF to (files whose name extension is "jpg") of eachSet as alias list
        repeat with c1 from 1 to count of imgF
            copy item c1 of imgF to end of fColl
        end repeat
    end repeat

-- Choose random images to open
    set rndImgs to {}
    repeat until (count of rndImgs) is 3
        set r1 to some item of fColl
        if rndImgs does not contain r1 then
            copy r1 to end of rndImgs
        end if
    end repeat
    open rndImgs
end tell

I've read somewhere that using System Events to open files rather than the Finder can be faster but I have no evidence of that.

| improve this answer | |
  • @Maockman Thanks! I see this does achieve the result I want. The problem is still speed. I have tested your script with 150+ images and it takes about 20 seconds to execute. If a shell script was used to fetch the jpg images instead of the Finder, I am pretty sure it can execute much faster. The problem here is that I will be using this script on a large amount of images (possibly more than 500 at a time) and I will like for this to be as fast and efficient as possible. – David Apr 11 at 17:22
  • Yeah, I was concerned about that too. With just a handful of files it was fine but I tried about 60 from three folders and its speed clearly fell off (but was 3.6 seconds). I guess this is one of those cases in which time increases exponentially as the file number increases. Compared to @user3439894's answer, it was about one-tenth the speed. – Mockman Apr 11 at 23:14
0

I was able to get find a simple script that does what I want in just a few lines of code:

tell application "Finder" to set theFolders to selection as alias list

repeat with aFolder in theFolders
    set the contents of aFolder to quoted form of POSIX path of aFolder & " "
end repeat

set theImages to paragraphs of (do shell script "find " & theFolders & " -iname '*.jpg' | sort -R")

repeat with aFile in theImages
    set contents of aFile to POSIX file aFile
end repeat

tell application "Finder" to open theImages
| improve this answer | |
  • See, now here, your shell code is search through folders and their subfolders recursively. But you said you didn’t have subfolders. Is that still the case? If so, and you’re sticking with the shell script, then find is not necessarily the most appropriate way to obtain your JPGs, nor the quickest, compared to a just getting the shell to print the files outright, i.e. do shell script "printf '%s\\n' */*.jpg | sort -n". However, if you’re after a short script, then do shell script makes yours three times the length it needs to be, as it just boils down to: – CJK Apr 23 at 17:15
  • First line of your script remains unchanged. Declare a variable as an empty list, call it L. Then the line in your repeat loop can be:tell application id "com.apple.SystemEvents" to set end of L to the path of every file in aFolder whose name extension is "jpg". Then skip everything else until the last line that opens L. You could have devised that yourself just by comparing the various offerings on this page. – CJK Apr 23 at 17:25
  • Hey sorry for not responding on time CJK. Been busy. When I said there are no subfolders, I was referring to the two folders I was testing on my desktop. They did not have subfolders and script was only pulling up a few files there. But when it comes to the script itself, I do not mind it searching through folders recursively. – David May 3 at 19:48

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .