• jm2466

    I wrote a program (as an extension) to take selected photos from the camera roll, combine them vertically and save the combined photo to the camera roll. This would be used for combining multiple screen shots of say a web page where you capture parts of the page while scrolling through it. I know you can buy an app(s) that do this but this seems like a perfect thing for Pythonista (plus an opportunity to learn).

    I have the code merging the photos (at least a few) but Pythonista crashes when more than 3 or so pics are selected and when trying to save to the camera roll. I added the startup code to log crash info but it is always empty. Looking for any help anyone may have.

    import appex
    from PIL import Image
    from PIL.ExifTags import TAGS
    import photos
    
    # standard get exif code - needed for image size
    def get_exif(fn):
        ret = {}
        i = Image.open(fn)
        info = i._getexif()
        for tag, value in info.items():
            decoded = TAGS.get(tag, tag)
            ret[decoded] = value
        return ret
    
    def main():
        if not appex.is_running_extension():
            print('This script is intended to be run from the sharing extension.')
            return
        images = appex.get_images()
        print(f'len(images) = {len(images)})')
        
        # get the widths/heights of the images
        widths, heights = zip(*(i.size for i in images))
    
        # this is stacking vertically so the width is the max width of all the pics
        max_width = max(widths)
        # since stacking vertically the height is the total heights of the pics
        total_height = sum(heights)
        print(f'total height = {total_height}')
        print(f'max width = {max_width}')
        
        # create a new blank image in the required size
        new_im = Image.new('RGBA', (max_width, total_height))
        print(f'new_im.size = {new_im.size}')
    
        # now assemble the merged pic
        
        # y_offset controls the pixel location of where each image is added
        # to the new image - to add each end-to-end
        # starting at the top which is y = 0
        y_offset = 0
        # loop through all passed images
        for im in images:
            # paste in the image at the left edge (x=0) and current offset for y
            new_im.paste(im, (0,y_offset))
            print(f'y_offset = {y_offset}')
            # increase the offset by the height of current pic
            y_offset += im.size[1]
    
        # save the new image to the camera roll
        photos.save_image(new_im)
    
    if __name__ == '__main__':
        main()
    

    posted in Pythonista read more
  • jm2466

    Thought I posted the code to merge two albums but it appears I did not. So here it is:

    import photos
    from datetime import datetime
    
    albums = photos.get_albums()
    
    # prob better ways to do this but this works, loop through albums and create list
    # where album name equals name in quotes, then take first one (should only be 1)
    sourcealbum = [a for a in albums if a.title == 'put album name here'][0]
    print(f'Nbr photos in sourcealbum: {len(sourcealbum.assets)}')
    
    sourcealbum_2 = [a for a in albums if a.title == 'put second album name here'][0]
    print(f'Nbr photos in sourcealbum_2: {len(sourcealbum_2.assets)}')
    
    # this requires that the dest album exists as it will not create it
    destalbum = [a for a in albums if a.title == 'put dest album name here']
    
    if len(destalbum) > 0:
        destalbum = destalbum[0]
    print(f'destalbum: {destalbum}')
    
    # first create a merged list of both albums
    mergedlist = sourcealbum.assets.copy() + sourcealbum_2.assets.copy()
    
    print(f'Nbr photos in mergedlist: {len(mergedlist):,}')
    
    # sort merged list by creation date
    mergedlistsorted = sorted(mergedlist, key=lambda x: x.creation_date)
    
    # add the sorted assets to the destination album
    destalbum.add_assets(mergedlistsorted)
    
    # I always put this at the end in scripts to know for sure when it finished
    print('***** DONE *****')
    

    So this merged to albums with about 900 pics in one and 800 in another almost instantly. The resulting album had 1700+ pics in the correct order and the pics were not physically duplicated. Do not know how I could have done this any other way on the phone. I do not sync my iphone with a Mac so if I did this on Windows I would lose the live photo part of the iphone pics.

    Thanks everyone that helped!

    posted in Pythonista read more
  • jm2466

    Thanks! That worked. Now just having a bit of an issue decoding the exif.

    Here is what I am trying to do. I have 2 albums from a recent vacation with a lot of photos in each. One is from my phone (all live photos) and the other is from my dslr. They are each in date/time order and I want to merge both into one album sorted by date/time (I want to do this on the phone as to not lose the live photos). This logic is fairly easy as long as I can get photos from the two albums (which I can now - thanks!) and get the exif info from each to compare (the dates outside the exif may not reflect the actual time the picture was taken). The Image.info appears to have the exif data but looks to be Unicode or something. Have not had a chance to look into it further yet so if you have a quick solution to this that would be great.

    Thanks again

    posted in Pythonista read more
  • jm2466

    I tried searching but it was not clear from what I found. I want to copy a photo from one album to another without creating a physical copy. In some of the examples it appears the image is opened, some reference a temp jpg on the file system or something. Anyhow I thought I could just get a reference/pointer to the photo from one album then add that reference to the other album. Anyone do this in straight python or does it require objc calls?

    Thanks

    posted in Pythonista read more
  • jm2466

    I tried a different sample app that uses alloc init and delegates and it works. Heard back from Ole and Pythonista is not a HomeKit app and it does appear it will become one. I am guessing that is why it is crashing. Thanks for the help.

    My use case is to provide a different UI and organization for HomeKit triggers based on how I am using it. I know how to develop apps in Swift and could go that route but I believe the free developer account apps are only signed for 7 days. This will not be a frequently used app and I could go that route (having to deploy it from Xcode each time I need to run it after 7 days from last run). Pythonista would have filled the need perfectly if it was HomeKit capable.

    posted in Pythonista read more
  • jm2466

    The delegate is created first and the init on it causes a crash. If I comment that line or remove the init then the HMHomeManager causes the crash with the init. Also tried new() with the same result.

    I did not get a popup asking for HomeKit access. I just checked in Xcode and HomeKit is a capability that needs turned on for the app (pythonista in this case as you mention). Ultimately it has to be set on the app id (Xcode will do this or can be done in the developer portal for the app id).

    Will send a support email asking about this.

    Thanks

    posted in Pythonista read more
  • jm2466

    Thanks for looking into this and the reply. I pasted your code into a new script file and unfortunately it is crashing pythonista at the alloc init again. If I take off the init it crashes when setting the delegate as before. I am using a different iOS device than yesterday but I appear to be having the same issue. I am running iOS 11.2.5 on the device I am trying it on now if that matters.

    posted in Pythonista read more
  • jm2466

    So I tried to add the delegate and added the fault handler. With the init removed it crashes when assigning the delegate.

    Here is my code:

    from __future__ import absolute_import, division, print_function
    from objc_util import *
    from pythonista_startup.enable_faulthandler import run
    
    load_framework('HomeKit')
    
    def homeManagerDidUpdateHomes(self, cmd):
        print('homeManagerDidUpdateHomes')
    
    methods = [homeManagerDidUpdateHomes]
    protocols = ['HMHomeManagerDelegate']
    MyHMHomeManagerDelegate = create_objc_class('MyHMHomeManagerDelegate',NSObject,methods=methods,protocols=protocols)
    
    def main():
        print('Start of script')
        # run enable faulthandler
        run()
        myHMHomeManagerDelegate = MyHMHomeManagerDelegate.alloc()
        HMHomeManager = ObjCClass('HMHomeManager')
        homemanager = HMHomeManager.alloc()
        homemanager.setDelegate(myHMHomeManagerDelegate)
        print('End of script')
    
    if __name__ == '__main__':
        main()
    

    NOTE: The objc delegate method is:

    - (void)homeManagerDidUpdateHomes:(HMHomeManager *)manager;
    

    When I tried to define the python delegate method as def homeManagerDidUpdateHomes(self, cmd,manager): I got an error that the method was only expecting 2 parameters but found 3.

    Back to the code... as mentioned I first ran with the line to set the delegate which crashed so I then commented out the set delegate line to let it complete successfully. The fault handler outputted the following:

    Start of script
    Enabling fault handler and Objective-C exception handler...
    Pythonista quit abnormally last time.
    For details, see the following log file: faultlog-2018-02-19-14-36-24.txt
    Setting fault handler.
    Done enabling fault handler and Objective-C exception handler.
    End of script

    The contents of the fault log

    Fatal Python error: Segmentation fault

    Current thread 0x000000016f72b000 (most recent call first):
    File "/var/containers/Bundle/Application/31989ED7-8359-46ED-90D1-AA84E7A73FA8/Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/objc_util.py", line 897 in call
    File "/var/containers/Bundle/Application/31989ED7-8359-46ED-90D1-AA84E7A73FA8/Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/objc_util.py", line 803 in call
    File "/private/var/mobile/Containers/Shared/AppGroup/0ED4DCFF-B233-4C51-A9EF-89FAA67A9C87/Pythonista3/Documents/Test01/HomeKit03.py", line 21 in main
    File "/private/var/mobile/Containers/Shared/AppGroup/0ED4DCFF-B233-4C51-A9EF-89FAA67A9C87/Pythonista3/Documents/Test01/HomeKit03.py", line 25 in <module>

    Here is the fault log when using alloc().init()

    Fatal Python error: Aborted

    Thread 0x000000016b577000 (most recent call first):

    I also tried .delegate = instead of setDelegate which crashed with the following log

    Fatal Python error: Segmentation fault

    Current thread 0x000000016f8a3000 (most recent call first):
    File "/var/containers/Bundle/Application/31989ED7-8359-46ED-90D1-AA84E7A73FA8/Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/objc_util.py", line 897 in call
    File "/var/containers/Bundle/Application/31989ED7-8359-46ED-90D1-AA84E7A73FA8/Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/objc_util.py", line 650 in setattr
    File "/private/var/mobile/Containers/Shared/AppGroup/0ED4DCFF-B233-4C51-A9EF-89FAA67A9C87/Pythonista3/Documents/Test01/HomeKit03.py", line 22 in main
    File "/private/var/mobile/Containers/Shared/AppGroup/0ED4DCFF-B233-4C51-A9EF-89FAA67A9C87/Pythonista3/Documents/Test01/HomeKit03.py", line 26 in <module>

    Thanks for the help.

    posted in Pythonista read more
  • jm2466

    Thanks for the reply, will take a look at the fault handler.

    My code is pretty much all I have at this point (I had a few lines after to get room info but commented that out to track down the crash). In the code I posted it will crash with init and not when it is removed - not the entire line just removing .init()

    posted in Pythonista read more
  • jm2466

    This is the first time I am trying objc_util so not sure if I am doing something wrong. I am trying to access my HomeKit setup and am starting with the following

    from objc_util import *
    load_framework('HomeKit')
    HMHomeManager = ObjCClass('HMHomeManager')
    homemanager = HMHomeManager.alloc().init()

    This is based on the objective c call:
    self.homeManager = [[HMHomeManager alloc] init];

    My code crashes Pythonista but removing init() does not. I am sure someone will say "then just remove it" but it appears in looking at other objc_util examples that it is expected to be called. Running without init does let me get the primary home but no rooms - the big "but" is I have not coded the delegate methods which appears to be useful.

    Anyhow at this point was wondering if anyone has done anything with HomeKit that they could share. Also wondering about the init() issue.

    Thanks

    posted in Pythonista read more

Internal error.

Oops! Looks like something went wrong!