omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    Welcome!

    This is the community forum for my apps Pythonista and Editorial.

    For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.


    Trying to figure out an appex crash

    Pythonista
    appex debugging
    2
    4
    3565
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • bg2b
      bg2b last edited by

      I've got a little script for making half-size screenshots that I'd like to use as an appex. When I invoke the script normally, it pops up a photo selector and everything works fine. If I go into my screenshots album, select some photos, and then try to invoke the script via the appex mechanism, it processes a few photos (typically 3-6) and then the Pythonista window closes with no further indication of what has happened. It's not deterministic; if I delete the partial results and go back and try it again with the exact same photos, sometimes it will work. Here's a little YouTube video showing what happens. There's no information left the the console unfortunately. I also tried enabling the faulthandler, but there's nothing written to the file that I give it. I've added various tweaks thinking that maybe there was some kind of race condition, things like writing the PNG files to separate filenames or putting a one second sleep at various spots. None of those made any difference. I'm at a bit of a loss right now, so any suggestions for what might be wrong or how to proceed in debugging would be welcome.

      import photos
      import appex
      import datetime
      from PIL import Image
      
      def main():
        if appex.is_running_extension():
          imgs=appex.get_images()
        else:
          screenshots=photos.get_screenshots_album()
          assets=photos.pick_asset(assets=screenshots, title='Select screenshots to shrink', multi=True)
          if assets:
            imgs=[a.get_image() for a in assets]
          else:
            imgs=[]
        if not imgs:
          print('No input screenshots')
          return
        album_name='Small Screenshots'
        albums=[a for a in photos.get_albums() if a.title == album_name]
        if albums:
          album=albums[0]
        else:
          album=photos.create_album(album_name)
        for (i, img) in enumerate(imgs):
          print(f'Shrinking screenshot number {i+1}...')
          size=img.size
          small_img=img.resize((size[0]//2, size[1]//2), Image.BILINEAR)
          filename='.temp.png'
          with open(filename, 'wb') as f:
            small_img.save(f, 'PNG')
          asset=photos.create_image_asset(filename)
          album.add_assets([asset])
          asset.creation_date=datetime.datetime.now()
        print('Done!')
      
      if __name__ == '__main__':
        main()
      
      
      1 Reply Last reply Reply Quote 0
      • zrzka
        zrzka last edited by

        Do you have Mac & Xcode access? If so, you can access your device logs (realtime). Connect your device, reveal logs and try to run your extension. Anything suspicious? Just thinking aloud, because of Application Extension documentation quote (section Optimize Efficiency and Performance):

        App extensions should feel nimble and lightweight to users. Design your app extension to launch quickly, aiming for well under one second. An extension that launches too slowly is terminated by the system.

        Memory limits for running app extensions are significantly lower than the memory limits imposed on a foreground app. On both platforms, the system may aggressively terminate extensions because users want to return to their main goal in the host app. Some extensions may have lower memory limits than others: For example, widgets must be especially efficient because users are likely to have several widgets open at the same time.

        Your app extension doesn’t own the main run loop, so it’s crucial that you follow the established rules for good behavior in main run loops. For example, if your extension blocks the main run loop, it can create a bad user experience in another extension or app.

        Keep in mind that the GPU is a shared resource in the system. App extensions do not get top priority for shared resources; for example, a Today widget that runs a graphics-intensive game might give users a bad experience. The system is likely to terminate such an extension because of memory pressure. Functionality that makes heavy use of system resources is appropriate for an app, not an app extension.

        It can explain why your script works normally outside of application extension context.

        1 Reply Last reply Reply Quote 0
        • zrzka
          zrzka last edited by

          Here's slightly optimised version, which allows me to process even 50 images at once on iPhone SE / iPad Pro. Still room for an improvement. Anyway, I think, that you script was killed by the system, because you hold multiple versions of images in memory. Time to rewrite get_attachments, get_images, ... as generators?

          import photos
          import appex
          import datetime
          import os
          from PIL import Image
          
          def ensure_album(name):
              albums = [x for x in photos.get_albums() if x.title == name]
              
              if albums:
                  return albums[0]
              
              print(f'Going to create {name} album')
              return photos.create_album(name)
          
          
          def appex_images():
              attachments = appex.get_attachments('public.image')
              for x in attachments:
                  image = appex._image_from_attachment(x)
                  if image:
                      yield image    
          
          
          def assets_images(assets):
              if assets:
                  for x in assets:
                      yield x.get_image()
                              
                  
          def main():
              if appex.is_running_extension():
                  images = appex_images()
              else:
                  album = photos.get_screenshots_album()
                  assets = photos.pick_asset(
                      assets=album,
                      title='Select screenshots to shrink',
                      multi=True
                  )
                  images = assets_images(assets)
              
              album = None
              
              for image in images:
                  if not album:
                      album = ensure_album('Small screenshots')
                                  
                  small_size = tuple(x // 2 for x in image.size)
                  print(f'Resizing image of size {image.size} to {small_size}')
                  small_image = image.resize(small_size, Image.BILINEAR)
          
                  print('Saving image to temporary file')
                  with open('.tmp.png', 'wb') as f:
                      small_image.save(f, 'PNG')
          
                  print('Creating asset from temporary file')
                  asset = photos.create_image_asset('.tmp.png')
                  album.add_assets([asset])
                  asset.creation_date = datetime.datetime.now()
                  
              try:
                  os.remove('.tmp.png')
              except FileNotFoundError:
                  pass
                  
              print('Done')
          
          if __name__ == '__main__':
              main()
          
          1 Reply Last reply Reply Quote 0
          • bg2b
            bg2b last edited by

            The appex resource limits would explain everything. I don’t have Xcode access to check, but it makes sense. Thanks for helping out a Pythonista newbie!

            1 Reply Last reply Reply Quote 0
            • First post
              Last post
            Powered by NodeBB Forums | Contributors