I have a macro that opens the “Save video to…” menu on YouTube, scrolls through the playlist list until it finds the target playlist (using a Found Image action), and then checks whether the video is already saved to that playlist — indicated by a checkmark next to the playlist name (again, using another Found Image action).
If it’s already saved, a “Already added to playlist” notification appears.
If it’s not yet saved, it clicks the playlist to save the video (i.e., ticks the checkbox).
Now, I have multiple macros like this, each targeting a different playlist. I thought this would be a good use case for a subroutine, so I tried to refactor the shared logic into a reusable subroutine macro. But it’s not working — either I’ve set up the subroutine wrong, or something in the calling macro or the subroutine itself is misconfigured. What am I missing here?
I am not sure, but I do notice that your fuzziness values are not the same. That might explain it.
The phrase "is not working" is pretty ambiguous. Are you getting an error? Did you check the values of the variables to see if they are being set correctly? Did you use the debugger to go step by step to see if all the variables and conditions are being calculated correctly?
They are not the same images, and I have tested that that’s the fuzziness required to detect each respective image. (Also th fact that the “single macro” works as normal suggests it’s not likely the issue)
No error messages. It stopped “working” at the while loop of the subroutine macro, it is not able to detect the image somehow I think, because otherwise there will be that green display over the image. Then it would display the Text "Already added to playlist" even though it is not true.
Yes I have checked that the values of the variables are set correctly.
I just used the debugger to go step by step. And it seems to go through each steps ok? Im not entirely sure how to read these:
Ok, I think I see a problem. In your subroutine macro, you have a while loop that says "While FIND_PlayList is Empty" but unfortunately there is nothing inside that loop that changes the value of that variable. Therefore the loop will either end immediately if the variable is not empty, or it will loop forever if the variable is empty.
I didn't study your macro deeply enough to understand if there are any other problems, but clearly this is a problem. Obviously you did not copy the code accurately from your first macro into your second macro.
It seems to me that what you really need is to be able to pass an image to a subroutine. But this is not possible in KM. I've asked for it a few times, but so far this feature has not been added.
My idea was for it to loop forever (i.e. scrolls) until the image is found. That’s when the value of the variable change from “empty“ to “not empty” and moves on to the next action.
You are likely to be right. But I just can’t poinpoint the mistake I made.
In any case, it is moot now that you said passing an image to a subroutine is not possible. That’s too bad.
Side Note: When I used the debugger, I had to first disable all the macros with periodic trigger (that repeat every 1 second) and macros that continuously monitor my screen. And I have to quit KM engine then reactivate it to actually disable those macros, which is a pain when I want to use debugger!
There are workarounds. If you want, I can explain a workaround. But frankly, the workarounds involve more work and you probably don't gain too much over making separate macros for each image.
No you don't have to do that. You can place the periodic trigger macros in a group and then just disable the group (and in some cases if a periodic macro is running you may need to terminate the running macro, since disabling a group does not stop a running macro.) This is what I do. Or in cases where the macro is in the same group, I just disable one macro temporarily.
The problem was that the variable Find_Playlist is not changing inside your while loop. In order to fix that, you need to perform the Find Image action inside your loop. But that defeats the purpose of your subroutine because you would need to place your image inside the subroutine.
...isn't doing anything useful. If%FoundImage% carries over from parent to subroutine (Airy? You know way more about image detection than I do) it's the same value as stored in the Saved_to_Music_Rap variable -- if it doesn't carry over then the token evaluates to an empty string.
No -- but you could set Named Clipboards to the images you want to find, instead of those "Find Image" actions in your main macro. The "Find Image" actions would be in your sub, using the Named Clipboards. The Clipboards act as Global variables.
Before going any further, have a re-read of the "Variables" section of the manual -- particularly the bit about scope. You want to be use local variables where possible -- even more so with subroutine parameters since the whole point of a sub is that it's reusable, and that may mean more than one macro tries to re-use it at the same time.
And yes, that directly contradicts the suggestion to use Named Clipboards as Globals But needs must when the devil drives, and context is important -- here, the context suggests you won't be running multiple instances of the sub at the same time.
If you did want to make the sub independent then, instead of using Named Clipboards, you could either:
Store the images in the caller macro and write them out to temporary files, passing the file path to the sub as a parameter which would use "Find Image" actions with "File: <filepath>" option
Store the images on disk, the caller passing the appropriate file paths as parameters, the sub using those in the same way as above
The first would give portability at the expense of macro plist bloat, the second keeps your plist slim but involves more management and a shared file-space (iCloud, Dropbox, etc) if you use these macros across multiple devices.
I have actually put all the periodic trigger macros into a group, then disabled both the group and the macros within it. I still need to quit KM engine in order for it to stop running
Sorry, I am not sure I get it because in my mind I thought that I don’t need the variable Find_playlist to change inside the while loop. I need it keep scrolling and only stop when the image (saved in the Find_playlist variable) is found, then the next action would be to save that image (But ofc like you said even what I said is true (which it’s not), subroutine doesn’t allow it to work that way). Set me straight please! .
Also, I have to take a detour and I’m going to drag you all into my BS. YouTube has just changed some part of the user interface (including the “save to playlist” stuff) today!!! And the “all-in-one” macro that I showed you at first which used to work fine, no longer works! (I have changed the images to adapt to the new interface but still doesn’t work). By not working, I mean it would continue scrolling even though it has scrolled past the playlist I intended to save (which is the image in the found image condition). I have done some testing to make sure that the image can actually be detected in that fuzziness.
Why did it used to work before, and now it doesn’t?? Does it have something to do with what you said earlier about not changing the variable value inside the while loop??
Here is the “all-in-one“ macro again (with the images changed to adapt to new YouTube interface but everything else remains the same):
For the record, this is why I want to use subroutine, so that if YouTube changes its interface or I need to make adjustment, I only need to do it once, rather than multiple times (because I have multiple macros like these). Yeah, so maybe a workaround would be nice. I’m going to try out the “Set named clipboards to image” method @Nige_S mentioned after I solve this annoying problem of mine!
No you don't have to stop the KM engine to stop running macros. You can simply stop all running macros from the Engine's menu, see "Cancel all Macros". In fact, there are even easier ways to stop all running macros, but this is what I recommend for you.
I'm happy to set you straight. The variable Find_playlist is set before your loop, but NEVER CHANGES inside your loop. Prior to the loop, you set it, but inside the loop, you didn't reset it by finding another copy of that image. You got it right in your first macro where you put the Find Image inside the loop. But in your subroutine you failed to search for the image inside your loop. Therefore the loop will either be always true or always false.
No problem. Perhaps you understand now why KM is based exclusively on public APIs, because Apple can change the private APIs any tie it wants, just like Google can change its Youtube GUI any time it wants. KM has been extremely stable for decade(s) now because KM does not rely on features that may change.
I have no idea. When you choose to rely on an unstable user interface, you will have to maintain your code on a constant basis.
Without seeing your exact screen in your exact web browser, I can't replicate the problem, so I probably can't help. But did you consider the possibility that you accidentally changed the zoom level of the web browser? That sort of thing will wreak havoc with Find Image actions. One simple click on a zoom in/zoom out button will cause your macros to fail permanently.
I suppose the HTML code of a webpage changes as well when the GUI changes? I am trying to see if maybe using the “execute JS script“ action instead of the “found image” action would require less maintenance that way
Nah I am sure I have not zoomed in/out.
GRHHH! let me just start from scratch and see if I can solve it.
There are ways to make macros that are more robust, and less reliable on images or Javascript.
The HTML code can change with or without GUI changes, and vice versa! There's no direct relationship. But more often than not they both change when the other does.
Lately I have found that instead of working with HTML or Images, using OCR is the most robust way to determine what the user sees. For example, whether the display is in dark mode or light mode, OCR will still see the same words, and that's not going to be true with your image searches. Also, OCR will still work regardless of your zoom level. So OCR has some advantages.
That makes sense. OCR sounds more reliable. I tried setting it up myself, but couldn’t get it to work properly. Could you show me an example of how you do it please?
Here's a simple example (which I just made up) followed by a very complex example.
In this simple example this code will launch the game Mini Motorways, then wait until the splash screens are all finished, which is "determined" when the screen contains a large "PLAY" button, which looks like the following. Notice that the words in this game are in GIANT fonts and can appear in any colours or brightness, and my code will still work. This is so much better than searching for images on the screen. You could try to use Find Image but that would be much more complex, and it's very doubtful that AppleScript (or Press Button) would work at all, but OCR is amazing for this task.
Now for the complex example. Here's a macro, below, that uses a form of recursion combined with OCR to find the location of a word on the screen: (it's normally not possible to find the location of a word in the screen, especially if the word can be in different sizes or colours)
Interesting. I play another game (Agar.io) and I want to press the button “Play“. What if I want the macro to click on the button (rather than using a keystroke like you did)? How would you do that then? This is my attempt, but clearly something wrong with it.
What is the expected result of OCRing an image? Given that, what would be stored in OCR_Test? How, if at all, does that relate to "a point to click the mouse at"?
Beyond that -- you can't OCR your screen to find out where something is (unless you use @Airy's"MoveMouseToWord" macro), rather it is for "is this text being displayed?".
And in your demo macro you aren't even searching the screen -- you're searching a static image that's part of the action, so the result will always be the same (he said carefully, trying not to give away the answers to previous questions...).
Nige already told you, but I'll repeat it, you CANNOT get the location of any word that is found on the screen using OCR. (Although I'm told another product, BTT can do that.) In your sample code you are assuming that the variable OCR_Test contains the location of the OCR results. No, it only contains the actual words from the OCR function. And in your code you are performing OCR on a fixed image, so you will always get the same result. You might as well just use "Set Variable to Text" and assign it the word "Play" because that also gives the same result.
I've never seen agar.io so I'm not sure how I would solve the problem.
Since in agar.io the “Play” button doesn’t change position (as long as the window size is the same), I could just do this (which is better than found image action because sometimes I do toggle between light/dark mode):
Yes, that's a solution. But you can improve on that solution. Since your action assumes the location of the button, you can speed up the action by replacing "all screens" with "area" and using the specific area. In fact, I would probably make it more clear by doing this: (by assigning the location of the button into a variable called LocalButton)
Ah Yes. I know to use “area“ when the image/OCR is confined to a specific region, picked that up from @Nige_S. I wasn’t doing that here because it’s a test hehe.
But I do want to ask…is there an advantage in saving the location of the button into a variable first like you did here? (Other than it’s probably make the code easier to read?)
Also, I recently made a macro like this to get the “coordinates” of an area, is there an easier / better way to go about this?
Not just easier to read, but it prevents needing to place the same values into two different parts of the action (the Click action and the Screen coordinates boxes.) In addition, it makes some of the additional ideas that I have in mind for future improvements possible.
There are many ways to get screen coordinates. That method you use isn't bad, although it takes a lot of typing to set up. My preferred method isn't the easiest, but it's quick: I just press CMD-SHIFT-4 and read the coordinates of the location where the mouse pointer is. There are many other ways, such as pressing CMD-5 in the KM Editor (and then checking that the upper left corner of the image in that window is blue.) Another way is to launch the macOS app Digital Color Meter.
I spilt the tea earlier (with one of my advanced macros) and you said you wanted to cut yourself (in post 14 above.) For this specific case, I want you to try my code before I show you more. If you had tried my code, there's already a tricky technique in it that you would have asked me about if you had tried it, so I'm pretty sure you haven't tried it yet.
I don't think it helps in this case. Because if you are using Find Image to find the button, you don't need OCR any more to detect the word, since the button's image itself contains the word. Once you do a Find Image, you have both the location and the detection of the button with the word. Therefore OCR isn't needed if you are already using Find Image to find the button.
Both OCR and Find Image are amazingly fast. In most cases, Find Image is perhaps 25% faster. But OCR is more robust, especially when the image can change a little bit (size, colour, font, etc.) You can use whichever approach you like.