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.


    Camera to DropBox

    Pythonista
    6
    13
    7722
    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.
    • reefboy1
      reefboy1 last edited by

      Is there a way to take a picture from my picture script:

      import photos
      x=photos.capture_image()
      photos.save_image(x)
      

      And save that photo directly to Dropbox?

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

        Pythonista has the Dropbox API (dropbox module) built-in, but Dropbox interaction is much more complicated than writing to a regular file. In order to be able to write to your own Dropbox your script needs to authenticate with your account data and an access token that you can get from your account settings. This thread explains the process in more detail.

        Once you have a usable DropboxClient object you can use the put_file method to upload a file. Note that put_file expects a "file-like" object, so you need to either first save the image to a temporary location in the script library, or store the raw image data in a StringIO object to simulate a file object.

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

          I would like to know how to save a camera roll image to the Pythonista script directory. Right now I copy the image to a buffer and then, using put_file, I transfer it to dropbox. I would like to be able to save it to the script directory first, then use put_file to get it to the dropbox at a later time. I can't get the buffer technique to save it to the script directory. Any help on this would be much appreciated.

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

            What kind of "buffer" are you using? If it supports normal file methods, you can easily change your code to write to a file instead:

            with open("my_file_name.jpg", "wb") as myfile:
                myfile.write(a_string_of_bytes)
            

            Reading from a file is similar:

            with open("my_file_name.jpg", "rb") as myfile:
                a_string_of_bytes = myfile.read()
            

            In both cases it is important that you access the file in binary mode ("rb" or "wb") - the default is text mode, which will not work with non-text files like images. Any code that uses the myfile object (the variable can of course have any name you want) needs to be inside the with block as well. Once the with block ends, the file is closed and can no longer be read from or written to.

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

              @coomlata1, this allows you to select a photo from the camera roll and save it into a local file.

              import photos
              assert photos.get_count(), 'Sorry no access or no pictures.'
              img, metadata = photos.pick_image(include_metadata=True, raw_data=True)
              filename = metadata.get('filename', 'my_photo.png')
              with open('my_photo.png', 'wb') as out_file:
                  out_file.write(img)
              print('Your photo was written to the file {}.'.format(filename))
              
              1 Reply Last reply Reply Quote 0
              • coomlata1
                coomlata1 last edited by

                Thanks for your comments and code...much appreciated. Here is the code I was working with:

                import photos
                from io import BytesIO
                import PIL
                from DropboxLogin import get_client
                from pexif import JpegFile
                
                choose=photos.pick_image(show_albums=True, multi=True, include_metadata=True)
                for photo in choose:
                    resized=photo[0].resize((1600, 1200), Image.ANTIALIAS)
                    buffer=BytesIO()
                    resized.save(buffer,'JPEG')
                    buffer.seek(0)
                    # Upload to dropbox folder..."new_filename" contains the path and filename for photo
                    response=drop_client.put_file(new_filename, buffer)
                

                This code works to add the photo to dropbox but with no metadata. When I resize the photo I loose all the metadata. I want to be able to write the metadata back to the photo before uploading to dropbox. The pexif module I imported to Pythonista will do that but it wants a relative reference to the photo.

                Trying your code, I created the file my_photo.jpg in the Pythonista scripts directory. I didn't try to resize it, but your code did keep all the media metadata intact. When I tried to access it with the pexif module to look at the metafile output I get a "TypeError:must be string without null bytes" as it dumps the metadata. I think it it is not matching the metadata included in the camera roll. Metadata seems to be a slippery slope.

                 ef=JpegFile.fromFile('my_photo.jpg')
                 ef.dump
                

                Pexif allows you to read and write metadata so I was hoping I could save the metadata with pexif and then write it back to the photo after resizing it.

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

                  The dump function should be dumping a complete list of all the JPEG segments. The EXIF data is an APP1 JPEG segment. Assuming the JPEG file is intact the error you are seeing may be due to a bug in Pexif. Maybe it is written in Python3 or something and does not work properly in 2.7. The traceback should show you where the problem is happening and allow you to determine the source of the problem.

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

                    The code above never calls buffer.close() so a buffer is left behind in RAM unused for every image processed. Use sys.getsizeof(buffer) to see how quickly this might add up to a lot of RAM.

                    I would recommend changing buffer=BytesIO() to with BytesIO() as buffer: and then indenting the lines that follow so that buffer.close() is called automatically for you.

                    See If you don’t use “with”, when does Python close files? The answer is: It depends.

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

                      Thank you much for the explanation...very helpful and informative!!! That would explain why Pythonista was crashing after processing 10 to 12 photos in the loop. Looping through a significant amount of photos, and processing them in this way, puts a heavy load on the memory resources in an iPhone.

                      Is it possible, using Pythonista, to resize a camera roll photo and either save it to the Pythonista script dir and/or upload it to dropbox without losing the metadata from the original photo on the camera roll? At this point I can save the photo, untouched, to the script dir and copy or upload it to dropbox and the meta is untouched. Any attempt to resize anywhere in the chain results in wiping the meta.

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

                        It appears that PiL doesn't respect exif data.
                        I found a pure python exif writing tool, you might be able to modify it for your purposes... Namely you'd want to read the exif before PILling it, then write it after.

                        http://www.fetidcascade.com/pyexif.html#x

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

                          @coomlat1 - the PIL code in Pythonista is vintage 2009. At that time exif support was "experimental". You will find that JPEG images have a _getexif function which parses the exif if it is there.

                          I checked the Pillow project to see if they have done any big changes to the JPEG code but don't see much: https://github.com/python-pillow/Pillow/blob/master/PIL/JpegImagePlugin.py

                          In fact they seem to be attempting to deal with crashes in _getexif only recently (Oct this year):
                          https://github.com/python-pillow/Pillow/issues/518

                          There was also this Pillow sample:

                          from PIL import Image
                          img_path = "/tmp/img.jpg"
                          img = Image.open(img_path)
                          exif = img.info['exif']
                          img.save("output_"+img_path, exif=exif)
                          

                          Tested in Pillow 2.5.3 - not clear is it works in PIL as well but is worth a try

                          For what it's worth here is a PIL example function that should autorotate an image based on the exif info it finds. You notice that it handles "exceptions" thrown by _getexif which will probably happen - but WTF its worth a try.

                          def exif_orientation(im):
                              """
                              Rotate and/or flip an image to respect the image's EXIF orientation data.
                              """
                              try:
                                  exif = im._getexif()
                              except Exception:
                                  # There are many ways that _getexif fails, we're just going to blanket
                                  # cover them all.
                                  exif = None
                              if exif:
                                  orientation = exif.get(0x0112)
                                  if orientation == 2:
                                      im = im.transpose(Image.FLIP_LEFT_RIGHT)
                                  elif orientation == 3:
                                      im = im.rotate(180)
                                  elif orientation == 4:
                                      im = im.transpose(Image.FLIP_TOP_BOTTOM)
                                  elif orientation == 5:
                                      im = im.rotate(-90).transpose(Image.FLIP_LEFT_RIGHT)
                                  elif orientation == 6:
                                      im = im.rotate(-90)
                                  elif orientation == 7:
                                      im = im.rotate(90).transpose(Image.FLIP_LEFT_RIGHT)
                                  elif orientation == 8:
                                      im = im.rotate(90)
                              return im
                          
                          1 Reply Last reply Reply Quote 0
                          • wradcliffe
                            wradcliffe last edited by

                            Update to my previous post: The Pillow sample:

                            from PIL import Image
                            img_path = "input_img.jpg"
                            img = Image.open(img_path)
                            exif = img.info['exif']
                            img.save("output_img.jpg", exif=exif)
                            

                            Does not work in Pythonista PIL. It does not throw exceptions, but the exif does not appear in the output JPEG. The Pillow guys seem to have added a raw exif buffer into the libraries.

                            So one possible solution would be for Pythonista to adopt Pillow and replace current PIL with it. PIL seems to be very stable and reliable but that is largely because it is abandoned by the developers. Pillow is still pretty active and supported. This is another Ole call since PIL/Pillow has a lot of C code in it. I wonder why the Pillow guys have not adopted pexiv2/gexiv2?

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

                              I finally got things working properly. I can now copy photos from the camera roll with it's metadata to the Pythonista script directory. Resize that photo and copy the metadata from the original photo back to the resized one using pexif, and then upload the resized photo with the metadata to Dropbox. Here is the script...Thanks everyone for input, info, and advice.

                              #coding: utf-8
                              import photos
                              import time
                              import Image
                              import sys
                              import console
                              import PIL
                              import string
                              from DropboxLogin import get_client
                              import pexif
                              
                              # Global arrays for photos that will require manual processing
                              no_exif=[]
                              no_resize=[]
                              
                              def GetDateTimeInfo(meta):
                              	old_filename=str(meta.get('filename'))
                              	exif=meta.get('{Exif}')
                              	try:
                              		if not exif=='None':
                              			theDatetime=str(exif.get('DateTimeOriginal'))
                              			
                              			theDatetime=theDatetime.split(" ")
                              		
                              			theDate=theDatetime[0]
                              			theDate=theDate.split(':')
                              		
                              			theTime=theDatetime[1]
                              			theTime=theTime.replace(':','.')+'.'
                              				folder_name=theDate[0]+'/'+theDate[1]+'.'+theDate[2]+'.'+theDate[0]
                              			new_filename=theTime+old_filename
                              		except:
                              			new_filename=old_filename
                              			folder_name='None'
                              			no_exif.append(old_filename)
                              	
                              		return old_filename,new_filename,folder_name
                              
                              def GetDimensions(meta,resize,img_name):
                              	# Original size
                              	exif=meta.get('{Exif}')
                              	img_width=exif.get('PixelXDimension')
                              	img_height=exif.get('PixelYDimension')
                              	
                              	if resize=='No Change':
                              		x=img_width
                              		y=img_height
                              		no_resize.append(img_name)
                              		return (x,y,img_width,img_height)
                              	else:
                              		resize=resize.split('x')
                              		x=int(resize[0])
                              		y=int(resize[1])
                              		
                              		# Don't resize photos smaller than your resize choice.
                              		if x*y>img_width*img_height:
                              			x=img_width
                              			y=img_height
                              			no_resize.append(img_name)
                              			return (x,y,img_width,img_height)
                              		
                              		# Don't resize if width or height isn't prportional to resize choice...scaling would be a better approach here.
                              		if x>img_width or y>img_height:
                              			x=img_width
                              			y=img_height
                              			no_resize.append(img_name)
                              			return (x,y,img_width,img_height)
                              			
                              	# Landscape
                              	if img_width>img_height:
                              		new_width=x
                              		new_height=y
                              	# Square
                              	elif img_width==img_height:
                              		# Don't resize if smaller than resize size
                              		if img_width<y:
                              			new_width=img_width
                              			new_height=img_height
                              			no_resize.append(img_name)
                              		else:
                              			new_width=y
                              			new_height=y
                              	# Portrait
                              	else:
                              		new_width=y
                              		new_height=x
                              	# Return resize dimensions...new & old	
                              	return (new_width, new_height,img_width,img_height)
                              
                              def CopyMeta(meta_src,meta_dst):
                              	# Copy metadata from original photo to a resized photo that has no media metadata and write the results to a new photo that is resized with the media metadata.
                              		
                              	# Source photo
                              	img_src=pexif.JpegFile.fromFile(meta_src)
                              	# Destination photo
                              	img_dst=pexif.JpegFile.fromFile(meta_dst)
                              	img_dst.import_metadata(img_src)
                              		
                              	# Results photo
                              	img_dst.writeFile('meta_resized.jpg')
                              	#img=pexif.JpegFile.fromFile('meta_resized.jpg')
                              	#img.exif.primary.ExtendedEXIF.PixelXDimension= 1600
                              	#img.exif.primary.ExtendedEXIF.PixelYDimension= 1200
                              	#img.writeFile('meta_resized.jpg')
                              		
                              	img_src=''
                              	img_dst=''
                              
                              def main():
                              	console.clear()
                              	
                              	try:
                              		# Here we are picking photos from the camera roll which, in Pythonista, allows us access to extra media data in photo's metafile. Because raw data is set to true, the image is a string representing the image object, not the object itself.
                              		choose=photos.pick_image(show_albums=True, multi=True,original=True,raw_data=True,include_metadata=True)
                              	except:
                              		print 'No photos choosen...exiting.'
                              		sys.exit()
                              
                              	# Create an instance of Dropbox client
                              	drop_client=get_client()
                              	
                              	count=0
                              	dest_dir='/Photos'
                              
                              	# When metadata is returned with photo the photo is a tuple, with one the image, and the other the media metadata.
                              	for photo in choose:
                              		print ''
                              		print 'Processing photo...'
                              		# Raw data string
                              		img=photo[0]
                              		# Metadata
                              		meta=photo[1]
                              
                              		# Get date and time info of photo
                              		old_filename,new_filename,folder_name=GetDateTimeInfo(meta)
                              		
                              		# Use info to rename photo
                              		new_filename=dest_dir+'/'+folder_name+'/'+new_filename
                              		
                              		# Get dimensions for resize based on orientation and size of original photo
                              		new_width,new_height,old_width,old_height=GetDimensions(meta,'1600x1200',old_filename)
                              		
                              		print ''
                              		print 'Original Name: '+old_filename
                              		print 'New Name: '+new_filename
                              		
                              		print ''
                              		print 'Original Size: '+str(old_width)+'x'+str(old_height)
                              		print 'New Size: '+str(new_width)+'x'+str(new_height)
                              		
                              		# Write string image of original photo to Pythonista script dir
                              		with open('meta_with.jpg', 'wb') as out_file:
                              			out_file.write(img)
                              		
                              		# Open image, resize it, and write new image to scripts dir
                              		img=Image.open('meta_with.jpg')
                              		resized=img.resize((new_width,new_height),Image.ANTIALIAS)
                              		resized.save('meta_without.jpg')
                              		resized=''
                              		
                              		# Copy metadata from original photo to resized one
                              		CopyMeta('meta_with.jpg','meta_without.jpg')
                              		
                              		print ''
                              		print 'Uploading photo to Dropbox...'
                              		
                              		# Upload resized photo with original metadata to Dropbox...use with statement to open file so file closes automatically at end of with.
                              		with open('meta_resized.jpg','r') as img:
                              			response=drop_client.put_file(new_filename,img)
                              		
                              		# Give Dropbox server time to process
                              		time.sleep(5)
                              		response=''
                              		
                              		print ''
                              		print 'Upload successful.'
                              		count=count+1
                              		
                              	print ''
                              	print str(count) + ' photos processed.'
                              
                              	if len(no_exif)>0:
                              		print ''
                              		print 'Photos with no DateTimeOriginal tag in their metadata and will need categorizing manually:'
                              		print '\n'.join(no_exif)
                              	
                              	if len(no_resize)>0:
                              		print ''
                              		print 'Photos that did not get resized because they were smaller than the resized version:'
                              		print '\n'.join(no_resize)
                              
                              	sys.exit()
                              if __name__ == '__main__':
                              			main()
                              
                              1 Reply Last reply Reply Quote 0
                              • First post
                                Last post
                              Powered by NodeBB Forums | Contributors