RhetTbull / PhotoScript

Automate Apple / MacOS Photos app with python. Wraps applescript calls in python to allow automation of Photos from python code.
MIT License
45 stars 5 forks source link

Cannot delete subfolder #35

Open anjos opened 1 year ago

anjos commented 1 year ago

I'm reaching an exception deleting a subfolder:

import photoscript

library = photoscript.PhotosLibrary()
A = library.create_folder("A")
B = library.create_folder("B", folder=A)
library.delete_folder(B)

will throw an exception on macOS 13.0 (22A380) with something like so:

Traceback (most recent call last):
  File "/Users/andre/work/anjos/photos/./move-albums-up.py", line 93, in <module>
    if __name__ == "__main__":
  File "/Users/andre/mamba/envs/photos/lib/python3.10/site-packages/click/core.py", line 1130, in __call__
    return self.main(*args, **kwargs)
  File "/Users/andre/mamba/envs/photos/lib/python3.10/site-packages/click/core.py", line 1055, in main
    rv = self.invoke(ctx)
  File "/Users/andre/mamba/envs/photos/lib/python3.10/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/andre/mamba/envs/photos/lib/python3.10/site-packages/click/core.py", line 760, in invoke
    return __callback(*args, **kwargs)
  File "/Users/andre/work/anjos/photos/./move-albums-up.py", line 86, in move_albums_up
    __import__("ipdb").set_trace()
  File "/Users/andre/mamba/envs/photos/lib/python3.10/site-packages/photoscript/__init__.py", line 459, in delete_folder
    return run_script("_photoslibrary_delete_folder", folder.id)
  File "/Users/andre/mamba/envs/photos/lib/python3.10/site-packages/photoscript/script_loader.py", line 18, in run_script
    return SCRIPT_OBJ.call(name, *args)
  File "/Users/andre/mamba/envs/photos/lib/python3.10/site-packages/applescript/__init__.py", line 110, in call
    return self._unpackresult(*self._script.executeAppleEvent_error_(evt, None))
  File "/Users/andre/mamba/envs/photos/lib/python3.10/site-packages/applescript/__init__.py", line 65, in _unpackresult
    raise ScriptError(errorinfo)
applescript.ScriptError: Photos got an error: Can’t get folder id "5AF03046-5BC8-4962-AB48-7E734934B264/L0/020" of folder id "75335F9A-8084-4D5F-9ED7-AC645A663CB0/L0/020" of folder id "7F82BB6F-9596-4E73-B4C5-6A3FDD9E1758/L0/020". (-1728) app='Photos' range=14097-14116

Any ideas on how to circumvent this issue?

anjos commented 1 year ago

For information, library.delete_folder(A) removes the whole hierarchy as expected.

RhetTbull commented 1 year ago

I’ll take a look. I think I know what’s going on. The Photos AppleScript interface doesn’t provide direct access to sub folders — the code will have to traverse the folder tree and tell the parent folder to delete the sub folder. May be a little while before I can get to this as I’ve got several other projects on the front burners.

RhetTbull commented 1 year ago

The easiest way to implement this might be this:

implement Folder().delete_folder() to allow a folder to delete its children.

In PhotosLibrary().delete_folder() check to see if folder has a parent and if so, ask the parent to delete the folder. Otherwise ask the library to delete the folder. This prevents having to traverse the whole tree and the library object itself doesn’t have access to sub folders.

RhetTbull commented 1 year ago

Looks like I tried to tackle this once before:

https://github.com/RhetTbull/PhotoScript/blob/3d64c526c8edbccc16f4d7da64214c019d7b4bc8/photoscript/photoscript.applescript#L516-L532

RhetTbull commented 1 year ago

As I've dug into this, I think it may not be possible to fix. This appears to be due to a bug in the Photos AppleScript interface introduced in Catalina and still remaining. Apple doesn't seem interested in fixing it (though I will file a bug report with them). Sub folders can be deleted on Mojave and below but apparently not on Catalina+

One possible workaround (that's a bit hacky) could be to recreate the folder and album structure while pruning the subfolder you want to delete. This would not be efficient but is likely possible to implement. I already do something similar for removing photos from albums (this is something not allowed by Photos but PhotoScript emulates this by creating a new album with the same name then deleting the original album).

anjos commented 1 year ago

Right. In my use-case, this issue does not annoy me that much. If too painful to fix, may be consider closing this as "won't fix".

RhetTbull commented 7 months ago

See this discussion on stack overflow

Under Monterey, at least, deletion of subfolders in Photos is not possible via AppleScript/JXA. There appears to be a bug in delete that fails for subfolders, but not for top-level folders and not for any-level albums.

tell application "Photos" --Subfolder deletion fails set folderToDelete to folder "Testing Folder Deletion" of folder "Posting Possibilities" of folder "Workshop" delete folderToDelete end tell We can verify both that folderToDelete contains a folder, and that this syntax for deletion is correct.

tell application "Photos" --It is getting the subfolder; we can test by getting the folder's name, parent, or id set folderToDelete to folder "Testing Folder Deletion" of folder "Posting Possibilities" of folder "Workshop" get id of folderToDelete end tell This returns the id of that folder; you can also try id of parent of folderToDelete or even (in this example, since "Testing Folder Deletion" is at the third level) id of parent of parent of folderToDelete. Clearly, folderToDelete is an actual item.

tell application "Photos" --Top-level folder deletions work set folderToDelete to folder "Top-Level Folder" delete folderToDelete

--Album deletions work regardless of location
set albumToDelete to album "Testing Album Deletion" of folder "Posting Possibilities" of folder "Workshop"
delete albumToDelete

end tell This will delete the top-level folder whose name is “Top-Level Folder”. It will also delete the sub-sub-album “Testing Album Deletion”. Clearly, the syntax is correct both for deleting folders and for deleting albums, including sub-albums. It would be exceedingly strange (though not out of the realm of possibility) for the syntax to change only for subfolders.

The same is true for JXA.

photos = Application("Photos") folderToDelete = photos.folders.whose({name: "Top-Level Folder"}) folderToDelete = folderToDelete()[0] photos.delete(folderToDelete) This will delete the top-level folder named “Top-Level Folder”.

I did the previous testing under macOS Monterey. While testing to see if this behavior remains the same under Ventura, I noticed a possibly related behavior. While you can get id of folderToDelete as noted above, you cannot get every folder whose id is… or get every folder whose name is…. It will only return top-level folders that match, even on an id search.

tell application "Photos" set folderToDelete to folder "Testing Folder Deletion" of folder "Posting Possibilities" of folder "Workshop" set folderId to the id of folderToDelete get the name of every folder whose id is folderId end tell This will return the empty string. But:

tell application "Photos" set folderToDelete to folder "Workshop" set folderId to the id of folderToDelete get the name of every folder whose id is folderId end tell This will return a list of folder names, which is of course a list of one since ids are unique. The same occurs with names.

tell application "Photos" get the id of every folder whose name is "Workshop" end tell "Workshop" is a top-level folder, and so this returns a list of ids. But whose name is "Testing Folder Deletion" will return an empty list.

I suspect that this behavior is related to AppleScript’s inability to reference folders by id.

There’s another bug, too. Noticing that the error is about “Can’t make folder id "xxx" of folder id "yyy" into type integer”, I tried deleting the folder by number. For example, get name of folder 5 of folder "Workshop" gave me the correct name of the example folder I’ve been trying to delete in my latest tests. But:

tell application "Photos" --deletion by index DELETES THE WRONG FOLDER get name of folder 5 of folder "Workshop" delete folder 5 of folder "Workshop" end tell This deletes the fifth top-level folder rather than either erroring out or deleting the fifth subfolder of “Workshop”. Fortunately, Edit:Undo Delete Folder successfully restores it.

Note that there is an interesting twist in JXA in which the result (using the syntax I’ve used) is always a list. This is probably because this is the equivalent of AppleScript’s get folders of folders of folders whose name is "Testing Folder Deletion". (Sadly, get folder of folders of folders whose name is "Testing Folder Deletion", while not a syntax error, returns a list of empty lists. It not only doesn’t return the requested folder, it also continues to return a list.)

This is more obvious when getting subfolders or subalbums. A subscript is required for each level down from the application.

folderToDelete = photos.folders.whose({name: "Workshop"}).folders.whose({name: "Posting Possibilities"}).folders.whose({name: "Testing Folder Deletion"}) folderToDelete = folderToDelete()[0][0][0] photos.delete(folderToDelete) Notice that three subscripts are required to get the actual folder, because this folder is at the third level (second sublevel). This will fail, just as it does in AppleScript. You can test that it really does have the folder in a manner similar to the test I used in AppleScript, by getting the folder’s properties, or the parent folder’s properties:

folderToDelete.id() folderToDelete.parent.id() Similarly, deleting sub-sub-albums does work in JXA:

albumToDelete = photos.folders.whose({name: "Workshop"}).folders.whose({name: "Posting Possibilities"}).albums.whose({name: "Testing Album Deletion"}) albumToDelete = albumToDelete()[0][0][0] photos.delete(albumToDelete) This uses the same syntax as the syntax that fails to delete a sub-subfolder but it successfully deletes an album at the same sub-level and with the same parent.

You may wish to specify your macOS version in the question. There is some evidence online that previous to Monterey this syntax did successfully delete subfolders. It is also possible that a post-Monterey OS will fix this, as it seems very likely to be a bug.